HTTP APIs

larsggu.meReference › webhook delivery

webhook delivery

POST {receiver_url} -> 2xx acknowledgement

An outbound HTTP request a service makes to a customer-supplied URL when an event occurs, replacing the need for the customer to poll for changes.

Description

A webhook turns the usual direction of an integration around. Instead of the customer asking, every few seconds, whether anything has happened, the service that owns the event sends one HTTP request to a URL the customer registered in advance. The request body carries the event; the response status carries the answer to a single question, which is whether the receiver has taken responsibility for that event.

That question is the whole contract. A receiver that returns a 2xx status is stating that the event has been durably written down somewhere it will survive a restart. It is not stating that the work implied by the event has been done. Receivers that do the work first and acknowledge afterwards hold the sending connection open for as long as their slowest downstream dependency, and they are the usual reason a delivery pipeline develops a queue it cannot drain.

Because the network is allowed to lose either the request or the response, a sender that does not see a 2xx has no way to tell which happened, and so it sends again. Redelivery is therefore normal rather than exceptional, and every receiver has to be written on the assumption that it will see the same event more than once. The identifier carried in the delivery headers is what makes that safe to handle; see the idempotency key entry for the general form of the pattern.

Retry schedules are ordinarily exponential with a bounded ceiling and a fixed give-up point, after which the delivery is parked rather than discarded, so that a receiver which was offline for a maintenance window can ask for the parked events once it returns. A schedule with no ceiling turns a brief outage on the receiving side into a sustained load spike when the receiver comes back.

Diagram of a webhook delivery: the sender raises an event, posts it to the receiver, and retries on an expanding interval until a 2xx acknowledgement is returned or the attempt budget is spent, after which the delivery is parked for replay.

Delivery, retry and parking. Only a 2xx ends the cycle.

Fields

Fields of webhook delivery
FieldFormMeaning
Delivery-Idopaque stringUnique per delivery attempt. Changes when the same event is sent again.
Event-Idopaque stringStable across every attempt at the same event. This is the value a receiver stores to detect a repeat.
Event-Typedotted stringNames the event so a receiver can dispatch without parsing the body first.
Signaturehex or base64 digestKeyed digest over the exact request body, computed with a secret shared at registration.
TimestampRFC 3339 instantThe moment the event was raised. Used together with the signature to bound replay.

Example

A delivery and its acknowledgement

POST /hooks/inbound HTTP/1.1
Host: receiver.example
Content-Type: application/json
Event-Id: ev_8fd21c
Delivery-Id: dl_41ab90
Event-Type: invoice.settled
Timestamp: 2026-09-06T09:14:22Z
Signature: sha256=4c1b...9ae0

{"event":"invoice.settled","object":"invoice","id":"in_3390","state":"settled"}

HTTP/1.1 202 Accepted
Content-Length: 0

The receiver answers 202 as soon as the event is written to its own store, before any downstream work begins.

Failure modes

  • Acknowledging after the downstream work rather than before it, which converts a slow dependency into a delivery backlog.
  • Comparing the signature against a re-serialised copy of the body rather than the exact bytes received, which fails for any receiver whose framework reorders keys.
  • Treating a redelivery as a second event, which double-posts whatever the event caused.
  • Returning 200 for a body the receiver could not parse, which hides a broken integration from both sides indefinitely.

Topic: Transport. Last modified 2026-09-06.