Webhooks Verification

Payload Verification

Requests will be signed using the HMAC-SHA256 algorithm with a symmetric key we store and share with you at the time of configuration.

A web hook event will be delivered with the request header “OrderGroove-Signature” which will contain

  • a timestamp of transmission
  • a hex encoded representation of a signature generated with HMAC-SHA256
OrderGroove-Signature: ts=1592570791,sig=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

It is strongly encouraged to verify the authenticity of events. A python example below shows how one might go about using the JSON values provided in the header to verify the signature provided in the header.

ts = "1592570791"
payload = u'<REQUEST BODY>'
sig_key = "shared symmetric key"
sig_input = "{ts}.{payload}".format(ts=ts, payload=payload)

hash_obj = hmac.new(key=bytes(sig_key, 'ascii'), msg=bytes(sig_input, 'utf-8'), digestmod=hashlib.sha256)
generated_sig = hash_obj.hexdigest() # “5257a869e7....”
return generated_signature == header_signature

As shown above by the sig_input variable, the signature is driven by

  • the timestamp provided in the request header
  • a period (.)
  • the payload of the web hook event

Let's look at more robust example.

Verification client/server example

Let's say you set up a webhook target URL in Ordergroove and we generate a verification key of

"super-secret-webhooks-verification-key"

Below you'll find a toy HTTP server written in python3. This represents your webhook target.

'''
This code is purely for educational purposes and should not be used
in any type of production/live environment.
'''

import hashlib
import hmac
import json
from http.server import BaseHTTPRequestHandler, HTTPServer

PORT = 8000
SHARED_SYMMETRIC_KEY = "super-secret-webhooks-verification-key"

def validate_signature(header_ts, header_signature, post_body):
    sig_input = f"{header_ts}.{post_body}"
    print(f'sig_input: {sig_input}')
    hash_obj = hmac.new(
        key=bytes(SHARED_SYMMETRIC_KEY, 'ascii'),
        msg=bytes(sig_input, 'utf-8'),
        digestmod=hashlib.sha256
    )
    generated_signature = hash_obj.hexdigest()
    print(f'{generated_signature} ?==? {header_signature}')
    return generated_signature == header_signature


class handler(BaseHTTPRequestHandler):
    def do_POST(self):
        print(self.headers)

        # POST body extraction
        content_len = int(self.headers.get('Content-Length'))
        post_body = self.rfile.read(content_len).decode("utf-8")
        print(f'post_body: {post_body}')

        # Signature extraction from header
        header_ts, header_signature = self.headers['OrderGroove-Signature'].split(',')
        print(header_ts, header_signature)
        _, header_ts = header_ts.split('=')
        _, header_signature = header_signature.split('=')
        print(header_ts, header_signature)

        # Set response headers
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

        # Verify signature from header and send response
        if validate_signature(header_ts, header_signature, post_body):
            self.wfile.write(bytes("SUCCESS", "utf8"))
        else:
            self.wfile.write(bytes("SIGNATURE VERIFICATION FAILED!", "utf8"))


with HTTPServer(('', PORT), handler) as server:
    print(f"Server starting - listening on port {PORT}")
    server.serve_forever()

If you save this to a file called server.py you can run this toy webserver with the command

python3 server.py

Now, here's a pre-made cURL request representing what Ordergroove would deliver to your target when an event is triggered.

curl -X POST -L http://localhost:8000 \
 -H "Content-Length: 25" \
 -H "Content-Type: application/json" \
 -H "OrderGroove-Signature: ts=1592570791,sig=08dc4769b5dc08d81447a2da752a4c0b0a2b1b36823eca6e7e92e65a25a722a1" \
 -d '{"a":{"webhook":"event"}}'

If you run this cURL command against our toy webserver, you will see SUCCESS as the response.

The signature in the header above was generated using the

  • ts value in the header
  • the POST body
  • the key "super-secret-webhooks-verification-key"

If you'd like to generate some of these values on your own, here's the source that should help you get on your way

import hashlib
import hmac
import json
import time

SHARED_SYMMETRIC_KEY = "super-secret-webhooks-verification-key"

event_payload = {'a': {'webhook': 'event'}} # You can modify this as you like
payload_str = json.dumps(event_payload, separators=(',', ':'))

ts = str(1592570791) # You can modify this as you like - use int(time.time()) if you prefer

sig_input = f'{ts}.{payload_str}'
sig = hmac.new(
  key=bytes(SHARED_SYMMETRIC_KEY, 'ascii'), 
  msg=bytes(sig_input, 'utf-8'), 
  digestmod=hashlib.sha256
).hexdigest()

print(f'''
curl -X POST -L http://localhost:8000\\
 -H "Content-Length: {len(payload_str)}"\\
 -H "Content-Type: application/json"\\
 -H "OrderGroove-Signature: ts={ts},sig={sig}"\\
 -d '{payload_str}'
''')

Key Rotation

If the rotation of a signing key occurred, there will be two sig fields present, providing one signature for each key.

sig=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd,sig=da32affa62cdca35257a869e7e56ff536d0ce8e108d8bdcebefa51cad7e77a0e