Master

The server calls you back

I polled an endpoint hoping to catch a change. A webhook inverts that: the server calls you when it happens. But the call can be forged, so you verify the signature first.

What you will see

402

About 2 min of reading

Try It

Receive a webhook and reject a forged one before acting.

1

A genuine event arrives with a valid signature. You act and answer 200.

curl -s -X POST -H 'X-Signature: ' -d '{"event":"charge.succeeded","id":42}' https://my.site/hook

Before you run it, say out loud what it will print. Then open the answer.

What it prints
200
2

A forged POST to the same URL has no valid signature. Reject it before acting.

curl -s -o /dev/null -w "%{http_code}" -X POST -d '{"event":"charge.succeeded"}' https://my.site/hook

Before you run it, say out loud what it will print. Then open the answer.

What it prints
402
Verify the signature over the exact raw bytes you received, not a re-parsed and re-serialised body. Re-serialising changes the bytes and makes a valid signature look wrong. Keep the raw body around to hash.

Data Flow

The service POSTs you an event when it happens; you verify the signature over the raw bytes, then act idempotently using the event id, and reply fast.
1 Service POSTs an event
↓
2 Verify signature on raw bytes
↓
3 Act idempotently by event id
↓
4 Reply fast or queue
Why this works
Act on verified events only

A webhook URL is public and forgeable. Never trust the body alone. Verify the signature over the raw bytes (the exact bytes received, not a re-serialised copy) before doing anything, and return a non-200 so the service knows to retry.

Be fast and idempotent

Handlers run on a hot path. Do the work fast or hand it to a queue, and make it idempotent using the event id, because services resend events. A handler that double-counts on a resend is the classic webhook bug.

Examples

Poll versus webhook

poll every 5s   ->  spam, late, wasteful
webhook         ->  one POST when it happens

Polling asks and waits. A webhook is the answer arriving when the event fires. One request, no loop, no delay.

The body you receive

{"event": "charge.succeeded", "id": 42}

The server POSTs you a body when the event happens. Your job is to read it and act, quickly and without blocking.

Verify before acting

expected = hmac(secret, raw_body)
if not hmac.compare_digest(sig, expected): return 402

Anyone can POST your URL. Recompute the signature over the exact bytes and reject a mismatch before you act.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← The browser gate
Previous Lesson
The browser gate
Next Lesson
Reading it as it arrives
Reading it as it arrives →