Your webhook URL is public. Anyone who finds it can post a courier.trip.completed,
so check the signature before you act on anything.
It is an HMAC SHA-256 of the raw body, keyed with your webhook secret, sent in
X-Webhook-Signature.
Read the raw bytes
The HMAC covers the exact bytes Gozem sent. Parse and re-encode, and your JSON writer will reorder keys or change spacing, and every signature fails.
Most frameworks discard the raw body, so ask for it explicitly.
Compare in constant time
== returns on the first differing byte. That timing difference is enough to recover a
valid signature one byte at a time.
Use hmac.Equal, or timingSafeEqual in Node. Reject and return; do not log anything
from a request that failed this.
Acknowledge, then work
Gozem retries if you do not return 2xx, and repeated failures disable the webhook. A
handler that writes to four tables before responding will eventually look like a
failure, so respond first and do the real work after.
recordEvent is your own idempotency store: it writes the guid and returns false if it
was already there. Retries mean the same event arrives twice, and a unique index on
that column is the whole implementation.
Before you ship
- Branch on
event_typeand ignore ones you do not know. New types get added, and a handler that500s on an unfamiliar one disables itself. - Return
5xxwhen your own storage is down. That is the one case where you want the retry. - Accept events over HTTPS only.
- A gateway that rewrites bodies in transit breaks the signature. Worth checking first if it passes locally and fails on deploy.
Full event list and retry schedule: Webhooks.