Empowering Restaurants through Integration

Follow

Authentication

In order to ensure that your POS systems are authorized to use the Grubhub API platform, every request you make must include two headers:

  • X-GH-PARTNER-KEY: {Partner Key}
  • AUTHORIZATION: MAC id="sv:v1:{client ID}",nonce="{generated nonce}",bodyhash="{base64-encoded hash of the request body payload}",mac="{generated MAC string}"

Some of these pieces of variable information (enclosed in curly braces) we will provide to you when you enter into the POS integration program -- the Partner Key and your client ID. For the others, you will need to algorithmically generate at the time of the request. These include the nonce, body hash, and the MAC.

In constructing the MAC string, you will need a Client ID, Secret Key, and Issue Date from Grubhub; the rest will be generated.

This topic will show you how to create each of the generated variables, then provide you with a Python script that will create the full authorization header for you. We can also provide Java and C# libraries upon request.

Our MAC authentication scheme follows the IETF standard.

Generating Authentication Information

To complete the AUTHORIZATION header, you'll need to include four pieces of information.

id

The id identifies both your client application and the scope and version that you are using. It always has three parts separated by colons. Currently, the current version uses sv:v1 as the first two portions of the id; the last portion is your client ID. For example:

id="sv:v1:c78ada21-62fa-11e5-ba00-43d58aece945"

You should have received a client ID from your representative at Grubhub. Contact us if you do not have that information.

nonce

This is a one-time use string that identifies this specific request uniquely. It must be unique across all requests. We recommend using something like the number of seconds seconds between when your ID was issued and the current system time and a random alphanumeric string, separated by a colon.

For example:

Nonce: "7349622:vCZfJEjW"

bodyhash

The bodyhash encodes the request body and the shared secret using a sha256 hash algorithm. This guarantees that you sent this request, as only you and Grubhub have access to this shared secret.

To get the string correctly to its final state, do the following:

  • Hash the body contents using sha256.
  • Encode both the hashed body and the shared secret using UTF-8 separately.
  • Encode the two strings into a single base64 string.

For example:

bodyhash="y676A2K+s1OQ9Qs7S+SMCtCAgzQ/FGip4LjD3EYURzI="

mac

The MAC is a normalized string version of several of the request elements encoded using a hmac-sha-256 algorithm and the shared secret. This ensures that the request has not been modified in transit or sent as a replay attack.

This MAC must contain the following information:

  1. The nonce.
  2. The HTTP request method in all caps.
  3. The HTTP request URI without query parameters.
  4. The port number of the request, which, by default, is 80.
  5. The request body hash as described above.
  6. An empty line, as we do not use the ext header.

The following Java code shows how these pieces of information can be normalized:

StringBuilder sb = new StringBuilder();

sb.append(nonce).append("\n")
  .append(requestMethod.toUpperCase()).append("\n")
  .append(requestUri).append("\n")
  .append(host.toLowerCase()).append("\n")
  .append(port).append("\n")
  .append(bodyHash).append("\n")
  .append(ext).append("\n");

String normalizedRequest = sb.toString();

When this normalized string has been hashed, it should look something like this in the header:

mac="wRVb8jpFEkH8F7FWkI0v8tdLb86BALO3U4AdVS+rXeU="

Example Header Script

To simplify your setup, we created a Python script that takes your request information and produces the authentication pieces for your header.

For this example, we need the following:

  • Client ID: "sv:v1:c78ada21-62fa-11e5-ba00-43d58aece945"
  • Secret Key: "qwfXhRvs6r5xJEEK37KO+qvSGvAijtJ/vG8xim6e+xo="
  • Issue Date: 1443126493378

Note that this is example information and will not work in your requests. If you do not have this information, contact Grubhub.

We're also assuming the following as the request:

Request body: null
Request method: GET
Request host: "pos-api-url.grubhub.com"
Request port: 443
Request path: "/pos/v1/merchant/11446280/orders"
Nonce: "7349622:vCZfJEjW"

The following Python script takes the above information and creates header data. To use this script in your own projects, replace the hardcoded data with your own.

import hashlib
import hmac
import base64

NEW_LINE = "\n"

client_id = "sv:v1:c78ada21-62fa-11e5-ba00-43d58aece945"
secret = "qwfXhRvs6r5xJEEK37KO+qvSGvAijtJ/vG8xim6e+xo="
issue_date = 1443126493378

nonce = "7349622:vCZfJEjW"
body = ''
request_method = "GET"
host = "pos-api-url.grubhub.com"
port = 443
uri = "/pos/v1/merchant/11446280/orders"
ext = ""

def generate_nonce():
    return nonce
def hash_body(body):
    if not body:
        return ""

    return base64.b64encode(hashlib.sha256(body).digest())

def normalize_request(nonce, request_method, request_uri, host, port, body_hash, ext):
    normalized_request = ""
    normalized_request += nonce
    normalized_request += NEW_LINE
    normalized_request += request_method.upper()
    normalized_request += NEW_LINE
    normalized_request += request_uri
    normalized_request += NEW_LINE
    normalized_request += host.lower()
    normalized_request += NEW_LINE
    normalized_request += str(port)
    normalized_request += NEW_LINE
    normalized_request += body_hash
    normalized_request += NEW_LINE
    normalized_request += ext
    normalized_request += NEW_LINE
    return normalized_request

def hash_normalized_request(request, secret):
    requestBytes = bytes(request).encode('utf-8')
    secretBytes = bytes(secret).encode('utf-8')
    signature = base64.b64encode(hmac.new(secretBytes, requestBytes, digestmod=hashlib.sha256).digest())
    return signature

def create_header_value(client_id, nonce, body_hash, ext, mac):
    header_value = ""
    header_value += "MAC id=\""
    header_value += client_id
    header_value += "\",nonce=\""
    header_value += nonce

    if(body_hash):
        header_value += "\",bodyhash=\""
        header_value += body_hash

    if(ext):
        header_value += "\",ext=\""
        header_value += ext

    header_value += "\",mac=\""
    header_value += mac
    header_value += "\""
    return header_value

# Generate a one time use nonce (hard-coded here, but should be something like seconds between issue date and system date, colon, a random alphanumeric string)
generated_nonce = generate_nonce()
print("nonce: " + generated_nonce)
# SHA256 hash the body and base64 encode the result
body_hash = hash_body(body)
print("body_hash: " + body_hash)
# Normalize the pieces of the request to ensure the hash, in the next step, matches what the service expects
normalized_request = normalize_request(nonce, request_method, uri, host, port, body_hash, ext)
print("normalized_request: "  + repr(normalized_request))
# HMAC SHA256 hash the normalized request using the client secret and base64 encode the result
hashed_normalized_request = hash_normalized_request(normalized_request, secret)
print("hashed_normalized_request: " + hashed_normalized_request)
# Generate the value that should actually be used in the "Authorization" HTTP header
header_value = create_header_value(client_id, nonce, body_hash, ext, hashed_normalized_request)
print("header_value: " + header_value)

The output looks like this:

python ./gh-hmac.py
nonce: 7349622:vCZfJEjW
body_hash:
normalized_request: '7349622:vCZfJEjW\nGET\n/pos/v1/merchant/11446280/orders\npos-api-url.grubhub.com\n443\n\n\n'
hashed_normalized_request: oePgS3fdPNPm3y/5KVuLIMVuxE3hTayBTYYqQUWYStQ=
header_value: MAC id="sv:v1:c78ada21-62fa-11e5-ba00-43d58aece945",nonce="7349622:vCZfJEjW",mac="oePgS3fdPNPm3y/5KVuLIMVuxE3hTayBTYYqQUWYStQ="

We have this example script downloadable in other languages here.