Forward Deployed Engineering Tutorial · Integration Engineering · Lesson 10 of 24
Webhooks
Receive events from customer systems safely: signature verification, replay protection, idempotency and fast acknowledgements.
How webhooks work
The customer's system (or a SaaS like Stripe, GitHub or Salesforce) sends an HTTP POST to your endpoint when something happens. You must:
- Verify the signature (usually an HMAC of the body with a shared secret) using a constant-time comparison.
- Reject old timestamps to prevent replays.
- Acknowledge quickly (2xx within seconds) and process asynchronously via a queue.
- Deduplicate by event ID: senders retry, so the same event can arrive more than once.
import hashlib, hmac, json
SECRET = b"whsec_demo"
processed = set()
def sign(body, ts):
return hmac.new(SECRET, f"{ts}.{body}".encode(), hashlib.sha256).hexdigest()
def receive(body, ts, signature, now):
if not hmac.compare_digest(sign(body, ts), signature):
return "401 bad signature"
if now - ts > 300:
return "400 too old (possible replay)"
event = json.loads(body)
if event["id"] in processed:
return "200 duplicate ignored"
processed.add(event["id"])
return f"200 queued {event['type']}"
body = json.dumps({"id": "evt_1", "type": "invoice.paid"})
good = sign(body, 1000)
print(receive(body, 1000, good, now=1010))
print(receive(body, 1000, good, now=1020)) # sender retried
print(receive(body, 1000, "forged", now=1010))
print(receive(body, 1000, good, now=2000))
Output
200 queued invoice.paid 200 duplicate ignored 401 bad signature 400 too old (possible replay)
Ordering
Events can arrive out of order. Use version numbers or timestamps on the resource, or fetch the current state from the API when an event arrives, instead of trusting event order.
Test yourself
Why must webhook handlers deduplicate by event ID?
Senders retry, so the same event can arrive more than onceEvent IDs are secretIt speeds up TLSJSON requires it
Show answer
Answer: Senders retry, so the same event can arrive more than once
At-least-once delivery is the norm for webhooks.