How I reverse engineered the Zomato app to build my own Order Tracking notification system

Hardik Srivastava
7 min readJun 17, 2024

--

(Disclaimer: I am an independent developer/person and I do not work in the Zomato engineering team)

Watch the final product in action here: https://youtu.be/E89Etnvq6rY

Check the code here: https://github.com/oddlyspaced/zomato-notification/tree/main

— — — — — — — — — — — — — — — — — — — — — — — — —

As a daily user of the Zomato Android app and an app engineer, I have grown to love the app’s design and the convenience it offers for ordering food from anywhere. However, I have always found it inconvenient to constantly open the app to check the status of my food delivery. While the app provides a great order tracking screen, it lacks the persistence and ease of access that the iOS variant of the Zomato app offers through its usage of the iOS Activity Indicator. Inspired by this, I decided to take matters into my own hands and reverse engineer the Zomato Android app to build a custom solution to enhance the order tracking experience. In this publication, I will share my journey of discovering the necessary API endpoints, designing the app’s system architecture, and implementing a persistent notification that provides near real-time order tracking information, all without the need to repeatedly open the Zomato app.

Part 1: Understanding the App Traffic

To first understand how the app can display the information, we need to figure out how the app is fetching the required information. A great starting point is to inspect the network traffic between the app and the server since it gives an accurate knowledge of the information that is being sent to the app. This would provide us with the information regarding the API endpoints that we can later try to call on our own independently to fetch the required order-related information.

For this, I made use of the following tools :

  • apktool
  • Charles Proxy
  • Android Emulator

The process of setting up an app for debugging is straightforward, so I won’t be covering it here, since that does not concern this publication.

You can reference the following publications to refer to the process :

Part 2: Analyzing the App Traffic

With our environment setup, we can now analyze the traffic and view the request contents. To find out the request for getting order history, we go to the order history page of the app, and simultaneously, view the activity in Charles. There seems to be a particular endpoint, that is being called every time we land on this page. It is this :

https://api.zomato.com/gw/order/history/online_order
Screenshot: Network activity of Zomato app on Order History screen

Let's check the response for this :

This is a trimmed response for the sample, the full sample response is here.

If we study this JSON structure, we can see that, there is a list of results which has an object with keys layout_config and order_history_snippet_type_2. The order_history_snippet_type_2 object contains information that is usable for us. The click_action object contains deeplink string, that we can use to extract the Order ID. The Order ID is present at other places as well, but this seemed to be a straightforward place for me. There are also top_container and bottom_container that contain all the other information that we need. The restaurant name can be extracted from the text item inside the title object in top_container. The Order ID can be simply extracted from the deeplink by replacing the starting tags and keeping the numbers. The order status can be extracted from text item inside the title object in top_container. The order delivery time can be extracted from text item inside the title object in bottom_container.

Similarly, if we place an order and analyze the traffic activity at the Order Details page in the app, the following API Endpoint is being called repeatedly :

https://api.zomato.com/v2/order/crystal_v2

The response for this endpoint contains a lot of private information so I won’t be sharing a sample response. However, for reference the following tags in the response give us the following information :

response -> order_details -> res_name = Restaurant Name
response -> order_details -> tab_id = Order ID
response -> header_data -> pill_data -> left_data -> title -> text = Estimated Time (String) [ex: Arriving in 5 mins]
response -> header_data -> subtitle2 -> text = Order Status in text [ex: On the way, Will be picked up soon etc]
response -> header_data -> pill_data -> right_data -> title -> text = Order Estimate Status [ex: On time, Delayed]

Part 3: Designing the Notification

With the data fetching part cleared out, I wanted to build a notification system that would draw inspiration from the iOS Activity Indicator. This is how Zomato makes use of the Activity Indication service in their iOS App :

Source: r/iphone subreddit on Reddit

I made the design with a similar layout for the Android notification as well. This is how it looked in the Figma design I made :

Sample design for app notification

Just like the iOS activity indicator, the notification created by my app would also display the restaurant name, a textual representation of the current order status, delivery time estimate status and the actual time estimate for the delivery handoff.

Part 3: Handling the flow of information and managing the notification

Diagram: Handling information between activity — service — notification

To have a notification that can be regularly updated, we need to make use of a Foreground Service along with some functionality to keep fetching the order details repeatedly. Let's call this service, “OrderTrackService”. The main purpose of this service would be to accept an Order ID, and call the crystal_v2 API to fetch the order status information and then display it in the notification. This task will be repeated at an interval of 30 seconds, and this process will run up until the order status becomes “Delivered”.

To handle the Notification, we will create a custom layout that will display the order information. The notification will be shown with a particular ID. In Android, if we resend a notification with the same ID, it automatically updates the existing notification that was being displayed in the notification panel. Thus we make use of this logic to fetch the order information and just recreate the notification and display it using the existing ID.

To control and start the service, we would also need an Activity, the main purpose of this activity is to fetch the order history and display it as a list. With this list, we can select what Order IDs would be passed to the OrderTrackService to handle the notification.

Part 4: Putting it all together

With the design and system clear in mind, it is time to bring the idea to life. I started by implementing the MainActivity using Jetpack Compose. The main activity consists of three buttons, the first button is to request the POST_NOTIFICATION permission for the app, the second button functions to start the Foreground Service, and the last button is used to fetch the order history. On tapping the “Fetch Orders” button, the app calls the online_order API and filters the results where the Order Status is not “Delivered”. The filtered orders are the orders that are currently active.

For actually calling the API endpoints, I implemented the retrofit library and created suitable interfaces with the required headers for the 2 API endpoints. To ensure code quality, everything here was implemented using the MVVM pattern, along with Hilt for Dependency Injection to make it easier to handle the Retrofit instance.

The OrderTrackService class is an extension of the Service class. As discussed we would handle the notification logic here. The communication between MainActivity and OrderTrackService is set up via Broadcast Intents. The corresponding button for each order in the list displayed in MainActivity sends a broadcast intent with the Order ID as a part of the data bundle for the Intent. Upon receiving this broadcast intent, the foreground service springs into action, calling the crystal_v2 API to fetch the order details for this specified Order ID. It then creates a notification that displays the information. The fetching and posting logic is made to work inside a Kotlin Flow, so that it can work asynchronously, with support for handling multiple notifications independently if required, since for every received broadcast with a valid Order ID, we launch a repeating flow. With Flow we can also pause/repeat the action as desired, thus this also allows us to repeat the same task with delays (30 seconds here in our case).

Final:

The custom app not only saves my time but also provides a much better way to stay informed about my order status. Since development, it has been very convenient for me to keep track of the order status without messing around in the app multiple times. I hope that the article provided decent insights into the process of reverse engineering and implementation of the network calls.

I have made the complete source code for this project available on my GitHub repository here :

https://github.com/oddlyspaced/zomato-notification/tree/main

Feel free to explore, experiment, and even contribute to the project if you have ideas for further enhancements.

--

--