Empowering Restaurants through Integration

Follow

Managing Orders

For a POS system to truly integrate with Grubhub on a day to day basis, it needs to be able to receive and manage orders that diners place via the web or mobile applications.

Order management is more than just receiving an order and sending it to the kitchen; you'll need to confirm or reject incoming orders, manage change or cancel requests, respond to managed delivery updates, and communicate updates to the Grubhub notification system. We'll walk through each of these processes (with API code examples) to show you what your POS system needs to implement.

This article assumes that you have already implemented the processes described in Creating Menus, and that your POS system user has built a menu in their local system. A restaurant will be unable to receive orders if it does not have a menu.

Receiving Orders

When a customer places an order online with Grubhub, the restaurant POS system can receive that order in one of two ways: polling or webhook notification.

Polling involves checking a specific endpoint to see if it returns new information. To get new, unconfirmed orders for a single merchant ID, call GET /merchant/{merchant_id}/orders?status=RESTAURANT_CONFIRMABLE. This will return all the orders that can be confirmed but have not yet been confirmed that have arrived within the previous 15 minutes. These orders should be presented to your users with the option to confirm them.

NOTE: If you are using polling, we recommend that you call the status endpoint every minute.

Here's an example of a polling request and the payload it returns:

GET /pos/v1/merchant/503736/orders?status=RESTAURANT_CONFIRMABLE HTTP/1.1
Host: api-third-party-gtm.grubhub.com
Content-Type: application/json
Authorization: MAC id="sv:v1:xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",nonce="xxxxxxxx:xxxxxxx",bodyhash="xxxxxxxxxxxxxxx=",mac="xxxxxxxx/xxxxxxxxxxxxxxxxxxxxxx="
X-GH-PARTNER-KEY: XXXXXXXXXXXXXXX

And the response:

[
    {
        "uuid": "d7cydHnLSX-A_tddhMLMGQ",
        "order_number": "567929975530583",
        "is_test": true,
        "status": "RESTAURANT_CONFIRMABLE",
        "statusHistory": [
            {
                "status": "RESTAURANT_CONFIRMABLE",
                "timestamp": "2017-10-24T19:28:20.707Z",
                "update_source": "DINER"
            }
        ],
        "updated_at": "2017-10-24T19:28:20.707Z",
        "merchant_id": "503736",
        "fulfillment_info": {
            "delivery_info": {
                "address": {
                    "address_line1": "17 W 10TH St",
                    "city": "Holland",
                    "state": "MI",
                    "country": "USA",
                    "zip_code": "49423",
                    "latitude": 42.788501,
                    "longitude": -86.1081
                },
                "is_managed_delivery": false,
                "estimated_delivery_time": "2017-10-24T19:58:20.705Z",
                "is_green_indicated": false,
                "instruction": "Pour some sugar on it.",
                "contact_info": {
                    "phone": "2345678901",
                    "name": "Orders BigTest"
                }
            }
        },
        "brand": "GRUBHUB",
        "time_placed": "2017-10-24T19:28:20.705Z",
        "confirmation_code": "4074",
        "when_for": "2017-10-24T19:58:20.705Z",
        "restaurant_timezone_id": "America/Chicago",
        "payments": {
            "payments": [
                {
                    "payment_type": "CASH",
                    "amount": 300
                }
            ],
            "total": 300,
            "adjusted_total": 300,
            "restaurant_total": 300,
            "restaurant_adjusted": 300
        },
        "charges": {
            "fees": {
                "total": 1,
                "delivery": 1
            },
            "taxes": {
                "total": 0,
                "sales": 0,
                "delivery": 0,
                "restaurant": 0
            },
            "tip": {
                "amount": 0,
                "type": "CASH"
            },
            "diner_grand_total": 300,
            "grand_total": 300,
            "restaurant_grand_total": 1,
            "line_groups": [
                {
                    "label": "",
                    "lines": [
                        {
                            "name": "Candy",
                            "special_instructions": "",
                            "line_options": [],
                            "price": 299,
                            "quantity": 1,
                            "menu_item_id": "25820792",
                            "diner_total": 0,
                            "total": 299,
                            "item_type": "",
                            "variation_id": ""
                        }
                    ]
                }
            ],
            "coupons": []
        }
    }
]

To receive webhook notifications, you'll need to contact us to configure the initial setup. After that, you will have to set up a REST API that can receive a webhook payload. Here's an example of what you can expect:

{
  "uuid":"UKBI2dGvRCamk2lEeCLiAw",
  "order_number":"ORD-16-1486752039496",
  "is_test":false,
  "status":"RESTAURANT_CONFIRMABLE",
  "statusHistory":
  [
    {
      "status":"RESTAURANT_CONFIRMABLE",
      "timestamp":"2017-03-16T15:30:39.801Z",
      "update_source":"GRUBHUB",
      "reason":null
    }
  ],
  "updated_at":"2017-03-16T15:30:39.801Z",
  "merchant_id":"630989",
  "fulfillment_info":
  {
    "delivery_info":null,
    "pickup_info":
    {
      "name":"Mrs Person",
      "contact_info":
      {
        "phone":"",
        "name":"Mrs Person"
      },
      "instructions":"",
      "estimated_pickup_time":null,
      "is_green_indicated":false
    }
  },
  "brand":"GRUBHUB",
  "time_placed":"2017-03-16T15:30:39.801Z",
  "confirmation_code":null,
  "when_for":"2017-03-16T15:30:39.801Z",
  "restaurant_timezone_id":"UTC",
  "payments":
  {
    "payments":
    [
      {
        "payment_type":"CASH",
        "amount":100
      }
    ],
    "total":0,
    "adjusted_total":100
  },
  "charges":
  {
    "fees":
    {
      "total":100,
      "delivery":100
    },
    "taxes":
    {
      "total":300,
      "sales":100,
      "delivery":100,
      "restaurant":100
    },
    "tip":
    {
      "amount":100,
      "type":"INCLUDE_IN_BILL"
    },
    "diner_grand_total":2104,
    "grand_total":2104,
    "line_groups":
    [
      {
        "label":null,
        "lines":
        [
          {
            "description":null,
            "name":"French Fries",
            "special_instructions":null,
            "line_options":[],
            "price":0,
            "quantity":1,
            "id":null,
            "menu_item_id":"123",
            "menu_item_uuid":"AAAAAAAAAHsAAAAAAAAAew",
            "diner_total":0,
            "total":299,
            "item_type":"Side",
            "variation_id":null,
            "tags":null,
            "external_id":"external_123"
          }
        ]
      }
    ],
    "coupons":[]
  },
  "catering":null
}

Confirming Orders

Once a restaurant has received an order, it then needs to confirm it. This step indicates to the diner that their order made it to the restaurant and is being prepared, optionally including the estimated time in which the order will be completed. A restaurant that doesn't confirm an order risks being removed from the Grubhub system.

Instead of confirming, a restaurant can reject the order, indicating that it will not or cannot complete the order. For the most part, this should only be used if your POS could not insert an order into the system.

To change the order status, call PUT /merchant/{merchant_id}/orders/{order_uuid}/status with a status of either CONFIRMED or REJECTED. For example:

PUT /pos/v1/merchant/503736/orders/d7cydHnLSX-A_tddhMLMGQ/status HTTP/1.1
Host: api-third-party-gtm.grubhub.com
Content-Type: application/json
Authorization: MAC id="sv:v1:xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",nonce="xxxxxxxx:xxxxxxx",bodyhash="xxxxxxxxxxxxxxx=",mac="xxxxxxxx/xxxxxxxxxxxxxxxxxxxxxx="
X-GH-PARTNER-KEY: XXXXXXXXXXXXXXX
{
  "status": "CONFIRMED",
  "wait_time_in_minutes": 20
}

If you set the status to REJECTED, you must also provide a reason_code. This code can be one of the following:

  • RESTAURANT_OFFLINE
  • MENU_ITEM_NOT_FOUND
  • PRICE_MISMATCH
  • OTHER

The confirmed response payload should look like this on success:

{
    "uuid": "_4v0JLXcRxukQQGJhMGnew",
    "order_number": "114620468915552",
    "is_test": true,
    "status": "CONFIRMED",
    "statusHistory": [
        {
            "status": "CONFIRMED",
            "timestamp": "2017-11-06T18:38:55.184Z",
            "update_source": "POS_GENERIC"
        },
        {
            "status": "RESTAURANT_CONFIRMABLE",
            "timestamp": "2017-11-06T18:37:59.277Z",
            "update_source": "DINER"
        }
    ],
    "updated_at": "2017-11-06T18:38:55.184Z",
    "merchant_id": "503736",
    "fulfillment_info": {
        "delivery_info": {
            "address": {
                "address_line1": "17 W 10TH St",
                "city": "Holland",
                "state": "MI",
                "country": "USA",
                "zip_code": "49423",
                "latitude": 42.788501,
                "longitude": -86.1081
            },
            "is_managed_delivery": false,
            "estimated_delivery_time": "2017-11-06T18:58:55.098Z",
            "is_green_indicated": false,
            "instruction": "This is a test order from GrubHub. Please confirm the order so we know you're able to receive orders from us.",
            "contact_info": {
                "phone": "2345678901",
                "name": "Orders BigTest"
            }
        }
    },
    "brand": "GRUBHUB",
    "time_placed": "2017-11-06T18:37:59.274Z",
    "confirmation_code": "3584",
    "when_for": "2017-11-06T19:07:59.274Z",
    "restaurant_timezone_id": "America/Chicago",
    "payments": {
        "payments": [
            {
                "payment_type": "CASH",
                "amount": 300
            }
        ],
        "total": 300,
        "adjusted_total": 300,
        "restaurant_total": 300,
        "restaurant_adjusted": 300
    },
    "charges": {
        "fees": {
            "total": 1,
            "delivery": 1
        },
        "taxes": {
            "total": 0,
            "sales": 0,
            "delivery": 0,
            "restaurant": 0
        },
        "tip": {
            "amount": 0,
            "type": "CASH"
        },
        "diner_grand_total": 300,
        "grand_total": 300,
        "restaurant_grand_total": 1,
        "line_groups": [
            {
                "label": "",
                "lines": [
                    {
                        "name": "Candy",
                        "special_instructions": "",
                        "line_options": [],
                        "price": 299,
                        "quantity": 1,
                        "menu_item_id": "25820792",
                        "diner_total": 0,
                        "total": 299,
                        "item_type": "",
                        "variation_id": ""
                    }
                ]
            }
        ],
        "coupons": []
    }
}

You'll need to update the status of orders at several other stages during the order process.

Updating Order Status

Once a restaurant confirms the order, then they can start making the food so a driver or a diner can pick it up. But they will still need to update Grubhub as the order passes through the fulfillment workflow. That includes notifying the diner or driver that the order has been prepared and is ready for pickup, that the order is out for delivery, and that the delivery has been made, if the restaurant manages their own delivery.

For this, we use the same endpoint as the confirmation action, PUT /merchant/{merchant_id}/orders/{order_uuid}/status, but use one of the following status codes:

  • PICKUP_READY - Ready to be picked up by either the driver or the diner.
  • OUT_FOR_DELIVERY - The driver has picked up the food and it is now on its way. This only applies to restaurants that manage their own deliveries.
  • FULFILLED - If the restaurant manages their own delivery, this indicates that their driver has delivered the order.

You only need to pass the status in the body for each of these three statuses. For example:

PUT /pos/v1/merchant/503736/orders/d7cydHnLSX-A_tddhMLMGQ/status HTTP/1.1
Host: api-third-party-gtm.grubhub.com
Content-Type: application/json
Authorization: MAC id="sv:v1:xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",nonce="xxxxxxxx:xxxxxxx",bodyhash="xxxxxxxxxxxxxxx=",mac="xxxxxxxx/xxxxxxxxxxxxxxxxxxxxxx="
X-GH-PARTNER-KEY: XXXXXXXXXXXXXXX

{
  "status": "PICKUP_READY"
}

This returns the full order JSON object, as shown in the previous section.

Managed Delivery Updates

If the order is a delivery fulfilled by Grubhub (as indicated by order.fulfillment_info.delivery_info.is_managed_delivery=true), then you can track the delivery status. You can either poll by order - GET /merchant/{merchant_id}/orders/{order_uuid}/delivery - by delivery - GET /deliveries/{delivery_id} - or by a group of restaurants - GET /group/{group_key}/deliveries, or contact your account representative to setup webhook notifications.

We expect most customers to check deliveries by order, so the example below shows how that works:

{
  "delivery":
  {
    "id":"xxxxxxxxxxx-xxxxxxx_xx",
    "account_id":"xxxxxxxxxxxxxxxxxxxxxxx",
    "confirmation_code":"xxxxxx",
    "trip_id":null,
    "name":"Pos delivery",
    "pickup":
    {
      "id":null,
      "name":"pickup",
      "address":
      {
        "street_address1":"Test St",
        "street_address2":null,
        "city":"Chicago",
        "state":"IL",
        "postal_code":"00000",
        "country":"USA"
      },
      "phone":"000-000-0000",
      "notes":null,
      "geo":
      {
        "lat":10.00000000,
        "lng":-10.00000000
      }
    },
    "dropoff":
    {
      "id":null,
      "name":"pickup",
      "address":
      {
        "street_address1":"Test St",
        "street_address2":null,
        "city":"Chicago",
        "state":"IL",
        "postal_code":"00000",
        "country":"USA"
      },
      "phone":"000-000-0000",
      "notes":null,
      "geo":
      {
        "lat":10.00000000,
        "lng":-10.00000000
      }
    },
    "flags":null,
    "contents":
    [
      {
        "description":"item",
        "size":null,
        "tags":null,
        "quantity":1
      }
    ],
    "status":"COMMITTED",
    "courier":null,
    "created":"2000-00-00T00:00:00.000Z",
    "times":
    {
      "pickup":
      {
        "type":"estimate",
        "timestamp":"2000-00-00T00:00:00.000Z"
      },
      "dropoff":
      {
        "type":"estimate",
        "timestamp":"2000-00-00T00:00:00.000Z"
      }
    },
    "committed_times":null,
    "cost":
    {
      "amount":100,
      "currency":"USD"
    },
    "collected_tip":
    {
      "amount":0,
      "currency":"USD"
    },
    "total":null,
    "preferences":
    {
      "pickup_time":null,
      "delivery_time":null
    },
    "metadata":
    {
      "grubhub_cust_id":"00000000",
      "grubhub_confirmation_code":"xxxxxx",
      "grubhub_has_alcohol":"false",
      "grubhub_needs_attention":"false",
      "grubhub_is_legacy_created_order":"false",
      "grubhub_order_type":"ASAP",
      "grubhub_not_for_biker":"false"
    },
    "alternate_name":null,
    "alternate_phone":null
  },
  "time_stamp":"2000-00-00T00:00:00.000Z",
  "delivery_status":"COMMITTED"
}

Handling Order Changes

Unfortunately, not every order goes perfectly, so your POS clients will have to make changes to orders on the fly. You can use the status change endpoint described in the previous section to update orders, including indicating when an order is ready to be picked up by a diner or driver, when an order is out for delivery, and, for self-managed delivery, when the order has been delivered.

If you want to change an order more drastically, you can call POST /merchant/{merchant_id}/orders/{order_uuid}/changerequests. This endpoint should be used to make changes that could affect the amount a diner must pay for the order, such as adding additional charges, refunds, and cancellations.

Here's what an adjustment looks like:

POST /pos/v1/merchant/503736/orders/_4v0JLXcRxukQQGJhMGnew/changerequests HTTP/1.1
Host: api-third-party-gtm.grubhub.com
Content-Type: application/json
Authorization: MAC id="sv:v1:xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",nonce="xxxxxxxx:xxxxxxx",bodyhash="xxxxxxxxxxxxxxx=",mac="xxxxxxxx/xxxxxxxxxxxxxxxxxxxxxx="
X-GH-PARTNER-KEY: XXXXXXXXXXXXXXX

{
  "amount": 1,
  "requestor_first_name": "John",
  "requestor_last_name": "Smith",
  "requestor_email": "jsmith+t2@grubhub.com",
  "restaurant_agent_name": "Some POS Partner",
  "reason": "Avocados are expensive, guys",
  "notes": "The diner added avocado in special instructions",
  "change_action": "adjust.action"
}

On success, this will send back the following:

{
    "order_id": "_4v0JLXcRxukQQGJhMGnew",
    "change_request_id": "UtpZENMXTge3Hp80N4jMWw",
    "request": {
        "amount": 1,
        "restaurant_agent_name": "Some POS Partner",
        "requestor_email": "jsmith+t2@grubhub.com",
        "requestor_first_name": "John",
        "requestor_last_name": "Smith",
        "reason": "Avocados are expensive, guys",
        "notes": "The diner added avocado in special instructions",
        "change_action": "adjust.action"
    },
    "status": "PENDING"
}

This change request must go through the Grubhub care team to be processed and applied to the diner's bill. You can use this same endpoint with the GET method to check the status of the change request.