Webhooks Explained: How Services Push Data to You Instead of You Polling
Polling an API every few seconds to check "did anything change yet?" wastes requests and adds latency. Webhooks flip the model: the other service calls you the moment something happens.
Published July 6, 2026A webhook is just an HTTP POST that one service sends to a URL you registered, carrying a payload that describes an event. There's no special protocol, no persistent connection, no library required on the receiving end — any endpoint that can accept a POST request can receive a webhook. What makes them tricky isn't the delivery mechanism, it's everything you have to do correctly once the request lands on your server.
Registering an endpoint
Setting one up is usually two steps: you give the sending service a URL, and it starts POSTing event payloads to it whenever something relevant happens — a payment succeeds, a file finishes uploading, a subscription renews. Compare that to polling GET /payments/status every 5 seconds until it changes: polling burns requests during the long stretches when nothing happens, and still introduces up to 5 seconds of delay before you notice a change. A webhook fires the instant the event occurs, with zero wasted requests.
Verifying the request actually came from who it claims
Your webhook endpoint is a public URL. Anyone who finds it can POST a fake payload claiming "payment succeeded" unless you verify the sender. The standard approach is HMAC signature verification: the sending service computes a hash of the request body using a shared secret key, and includes that hash in a header (commonly X-Signature or similar). You recompute the same hash on your end using the same secret and compare:
import hmac, hashlib
def verify_signature(payload_body, signature_header, secret):
expected = hmac.new(secret.encode(), payload_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header)
Note the use of compare_digest instead of ==. A plain string comparison returns as soon as it finds a mismatched character, which leaks timing information an attacker could use to guess the correct signature byte by byte. Constant-time comparison closes that side channel.
Respond fast, process later
The sending service is waiting for your HTTP response before it considers the delivery successful. If your endpoint does expensive work synchronously — resizing an uploaded image, sending three follow-up emails, writing to four tables — and that work takes longer than the sender's timeout (often 5–30 seconds), the sender marks the delivery as failed and retries, even though your server actually received it fine and is still working on it.
The fix is to acknowledge immediately and defer the real work: validate the signature, write the raw event to a queue or a database row, return 200 OK, and let a background worker pick up the event and do the slow processing. This also means a bug in your processing logic doesn't cause the sender to keep retrying a delivery you already received successfully.
Idempotency: the same event can arrive twice
Webhook senders generally guarantee at-least-once delivery, not exactly-once. A network blip after your server sent 200 OK but before the sender received it will cause a retry, and now you've received the same event twice. Every webhook payload should include a unique event ID; store IDs you've already processed and skip duplicates. This is the same deduplication pattern used for message queue consumers — treat a webhook exactly like a queued message that might be delivered more than once.
Handling out-of-order delivery
Retries and multiple delivery attempts mean events for the same resource can arrive out of order. If you receive "subscription cancelled" before "subscription created" because the first attempt at "created" timed out and got redelivered later, naive processing corrupts your state. Include a timestamp or sequence number in the payload and check it against what you've already recorded before applying an update — if the incoming event is older than the state you already have, discard it rather than overwriting.
Local development without a public URL
Webhook senders need a URL they can reach over the internet, which is awkward when you're developing against localhost. Tunneling tools forward a public HTTPS URL to your local machine so you can receive real webhook deliveries while debugging, without deploying anything. Most providers also offer a way to replay a past event, which is far less fragile than trying to trigger the real-world condition (an actual payment, an actual file upload) every time you want to test your handler.
What to log
When a webhook handler misbehaves, the sending service's dashboard is often your only visibility into what was actually sent, since you don't control the sender. Log the raw payload and headers for every delivery you receive, before any processing, in a place you can retrieve later. When a customer says "I never got my confirmation email," the first question is whether the webhook arrived at all, and that's only answerable if you kept a record independent of whether your processing logic succeeded.