Securing webhooks: signature verification and idempotency are most of the job
A webhook is an unauthenticated POST from the internet that you are about to act on. Two controls do most of the work of making that safe: verify the sender signed it, and make processing it idempotent. Get those two right and most webhook incidents never happen.
Verify the signature, do not trust the payload
A webhook arrives claiming a payment succeeded. Anyone who knows your endpoint URL can send that same shape. The control is a signature: the provider signs the raw request body with a shared secret, usually HMAC-SHA256, and puts the result in a header. You recompute the HMAC over the exact bytes you received and compare. If it does not match, you reject before doing anything else.
Two details decide whether this actually works. Compute the HMAC over the raw body, not a re-serialized version, because re-serializing changes bytes (key order, whitespace) and breaks the comparison for a legitimate request. And compare with a constant-time equality check rather than ordinary string equality, so the comparison does not leak the expected signature through timing.
Make processing idempotent
Providers retry: on your timeout, on a 5xx, and sometimes they simply deliver the same event twice. If processing a payment notification twice credits an order twice, the retry that was meant to add reliability has created a financial bug. The defense is a natural key from the event (the payment source plus the provider's transaction reference) with a unique index, checked before you write. Seen it already: return success and stop. Not seen: insert and process. The provider can deliver the same event ten times and your ledger gains one row. (Idempotency is a topic of its own; this is its highest-stakes use.)
Acknowledge fast, process after
A sender treats a slow response as a failure and retries, so slow processing manufactures duplicates. Acknowledge receipt with a 2xx as soon as you have verified the signature and persisted the raw event, then do the downstream work asynchronously. The handler's job is to authenticate and record, not to finish every effect inline.
Replay and freshness
A captured valid webhook can be replayed later. If the provider includes a timestamp in the signed payload, reject deliveries outside a tolerance window so an old captured request cannot be resent days later. The signature proves the request is authentic; the timestamp proves it is fresh.
The short version
Verify the HMAC over the raw bytes in constant time, dedupe on a natural key with a unique index, acknowledge fast and process async, and reject stale timestamps. That handful of controls is the difference between a webhook endpoint and a liability pointed straight at your database.
Where AgentKick fits
We build the integration and reliability layer for systems that take money or instructions over webhooks, where a forged or duplicated event has real consequences. If you are wiring up payment, banking, or partner webhooks and want them hardened before they touch your ledger, that is the work we do, usually as a short scoping engagement into a phased build.