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