8 min
  • APIs
  • Authentication
  • HTTP

Webhooks: when the API calls you

Every request so far in this track went from your code to somebody's API. A

webhook reverses the arrow: you register a URL, and the API POSTs to it when something happens.

That single reversal changes every assumption you have been working under.

PollingWebhook
You are the clientYou are the server
You choose when to askYou are called whenever, including 3 a.m.
A failure is yours to retryA failure is theirs to retry — at you
You authenticate to themThey must authenticate to you
Loading the API Visualizer simulation…

At-least-once is the only guarantee on offer

Look at what happened in that run: a failure, a silence, a request for more time, and finally an acceptance. Each attempt was re-signed, and each carried the same event id.

The silence is the interesting one. When the sender times out, it cannot distinguish "the receiver never got it" from "the receiver processed it and the reply was lost". So it retries — and the receiver may well execute the same event twice.

There is no protocol fix for this. Exactly-once delivery is not available; exactly-once processing is, and you build it by making your handler

idempotent, keyed on the sender's event id and never on anything you compute yourself.

Check yourself
Your webhook endpoint returns 400 Bad Request because the payload failed your schema check. Should the sender retry?

Signatures, and verifying before you parse

Your endpoint is a public URL, so anyone can POST to it. The signature is what separates a real event from a forgery, and the details matter:

text
signature = HMAC(secret, timestamp + "." + raw_body)

The timestamp is inside the signed material, so an old capture cannot be replayed with a fresh timestamp bolted on. Every retry is signed afresh while the event id stays constant — which is exactly what makes deduplication possible.

Two implementation rules that are easy to get wrong: compare in constant time, or the comparison itself leaks the correct signature one byte at a time; and verify before parsing, since a parser is a much larger attack surface than an HMAC comparison.

Predict what happens next
Your webhook handler does thirty seconds of work before responding 200. What does the sender do?

Key takeaways

  • A webhook reverses the roles: your endpoint is the server, and it must be public, fast and always available.
  • A timeout is indistinguishable from a lost reply, so at-least-once is the only delivery guarantee available.
  • Exactly-once processing is yours to build, by keying an idempotent handler on the sender’s event id.
  • The signature covers timestamp and raw body together, which is what stops an old capture being replayed.
  • Verify in constant time before parsing, acknowledge immediately, and do the real work on a queue.

Take it further