Webhooks & Events

Designing a Webhook Receiver

A good receiver acknowledges fast, validates carefully, and hands the real work to something that can take its time.

What you'll learn

  • Outline the responsibilities of a webhook endpoint
  • Separate fast acknowledgement from slower processing
  • Apply validation before any business logic runs
  • Plan for failure and observability from the start

7 min read

What the endpoint must do

A webhook receiver has a small set of clear jobs: accept the incoming request, confirm it is genuine, store or enqueue the event, and return a status code quickly. Everything else — updating records, sending emails, calling other services — should happen after you have acknowledged the delivery, not while the provider is still waiting on the line for your reply.

Keeping the endpoint thin matters because providers expect a prompt response. If your handler does heavy work inline, slow processing can push you past the provider's timeout, which is then read as a failure and triggers a retry. That manufactures duplicate deliveries you never needed. A lean endpoint that defers its real work avoids that entire class of problem, and it is the single most important design decision you will make here.

Acknowledge first, process later

The durable pattern is straightforward: verify the request, persist the raw event, return 2xx, and only then process asynchronously. Persisting first means that even if processing crashes halfway through, the event is already safe and can be retried from your own queue without involving the provider again.

function handle(request) {
  if (!verifySignature(request)) return respond(401);
  const event = parse(request.body);
  queue.enqueue(event);     // durable handoff
  return respond(200);      // acknowledge promptly
}

A background worker then performs the slow work at its own pace, with the provider long gone. This split between accepting and processing is the backbone of every reliable receiver, and it pays off the first time something downstream misbehaves.

Validate before you trust

Treat every incoming request as untrusted until you have proven otherwise. Verify the signature, check that the timestamp is recent, and confirm the payload matches the schema you expect. Only then should any business logic run on it. Skipping validation invites spoofed, replayed, or simply malformed events into the heart of your system, where they are far harder to deal with.

Validation also means deciding what to do with events you do not recognise. New event types may appear over time as the provider's product grows, so your receiver should ignore unknown types gracefully rather than erroring on them. The signature side of validation is covered in depth in verifying webhook signatures, which every receiver should follow closely before it trusts a single byte.

Build for failure and visibility

Assume from the outset that deliveries will sometimes fail, arrive twice, or come out of order — because they will. Design idempotent processing so that duplicates are harmless, and log every delivery alongside its event id so you can trace exactly what happened later. Good logs turn a mysterious three-in-the-morning incident into a quick lookup by id.

Expose a simple health check so you can confirm the endpoint is alive, and record basic metrics: deliveries received, processed, and failed. Those numbers tell you at a glance whether something is wrong before users do. For the broader contract you are coding against, including event shapes and retry behaviour, consult the API documentation as your authoritative reference.

Key takeaways

  • Keep the endpoint thin: verify, persist, acknowledge, then process
  • Acknowledge with a 2xx promptly to avoid timeout-driven retries
  • Validate signature, timestamp, and schema before business logic
  • Design idempotent processing and log every delivery for tracing

FAQ

Should I process the event inside the request handler?

Only the lightest work. The safe pattern is to persist or enqueue the event, return a 2xx quickly, and let a background worker do the heavy processing afterwards.

What status code should a successful receiver return?

Any 2xx, commonly 200 or 202. Reserve non-2xx codes for genuine failures you want the provider to retry, and 401 for failed verification.

How do I handle event types I do not recognise?

Ignore them gracefully and return a 2xx. Providers add new event types over time, so an unknown type should not cause your endpoint to error.

Integrate with Merion

Ready to build?

Read the API reference, grab the OpenAPI spec, and ship a resilient integration.