Skip to main content

Webhook Delivery

Webhook Delivery signs each event, retries failures for three days and records every attempt, so your receiver needs no retry logic of its own.
The platform records each attempt, and the receiver deduplicates.The platform records the event first, and the delivery machinery works from that row rather than from memory. It then signs an envelope, whose headers follow the Standard Webhooks scheme, giving the message an identifier, a signature and a timestamp, with the platform adding its own delivery identifier and endpoint identifier beside them. The platform leases the row for the attempt, and the POST and its database record are separate effects. A crash frees the lease, and the next scan reclaims the row: a send whose outcome was never recorded can repeat. Each attempt has twenty seconds to finish, and one of three outcomes follows. A 2xx reply means the consumer accepted the message and the delivery ends there. A retryable failure goes back on an exponential curve that starts near thirty seconds and doubles, with the platform multiplying each delay by a random factor between 0.7 and 1.3. A terminal failure stops at once because repeating it cannot help, meaning a 4xx reply other than 408 or 429, a counter-signed receipt with a valid signature but a body hash that does not match, or an address the platform's destination policy refuses. Only the retry branch keeps going until it hits the three-day cutoff: retrying stops three days after the platform recorded the event, and the platform writes that abandonment as a warning log and a failed span rather than dropping the event silently.The platform records each attempt, and the receiver deduplicates.The same figure as the desktop variant, drawn portrait for phone widths. The platform records the event before it signs an envelope with the Standard Webhooks headers plus its own delivery and endpoint identifiers. The leased attempt is the focal step. Recovery reuses recorded outcomes, but an unrecorded send can repeat. Each attempt has twenty seconds, and one of three outcomes follows: delivered on a 2xx reply, retried on a jittered backoff curve starting near thirty seconds, or terminal on a 4xx other than 408 or 429, a failed counter-signed receipt, or an address the destination policy refuses. Only the retry branch keeps going until it hits the three-day cutoff, and the platform logs that abandonment instead of letting it pass unnoticed.

Recorded attempts support recovery, but a response loss or crash before recording can still cause a duplicate, so deduplicate by webhook-id.

  • A brief outage at your receiver does not cost it the notification. A retryable failure is retried with jittered backoff, starting near thirty seconds, for up to three days from the moment the platform recorded the event. A Retry-After header on a failed reply raises the next wait to at least that long, within the time left.

  • A 4xx other than 408 or 429 is terminal, and so is a counter-signed receipt with a valid signature but a body hash that does not match. An address the platform refuses is terminal too, and nothing is sent. Correct the endpoint URL, then choose “Resend” on the failed event in the Console.

  • Recovery reuses recorded attempts, so a crash rarely resends what already worked. A POST your receiver got before the platform recorded it can still arrive twice, which is why webhook-id is the field to deduplicate on.

What the platform does and what your receiver does

The platform sends each event to your endpoint as a signed HTTPS POST, retries the failures worth retrying for three days, and records every attempt. Your receiver verifies the signature, deduplicates on webhook-id, answers 2xx once it holds the message, and handles the event's finality state.

The three days run from the moment the platform recorded the event, not from the first attempt. An event that waited before its first attempt has less than three days of retrying left. Attempt and delivery times are the platform server's clock, not the time of the block that produced the event, so they can read later than the newest block on an idle network.

The signature lets your receiver establish two things without trusting the network: that the platform sent the message, and that nothing altered it in transit. It does not tell you the fact is final. Check the event's finality state and your reconciliation requirements before acting on it.

Why a notice needs its own delivery system

A Platform Event is a fact the platform has already recorded. A recorded event tells nobody outside the platform anything until somebody looks. Without a delivery system, a receiver polls the platform's read APIs and diffs the answer, or relies on a bare callback URL with no retry, backoff or record of what was sent. Both put the retry logic, the endpoint health tracking and the record of delivery on the receiver.

Webhook Delivery keeps that work on the platform side. It owns the retry schedule, the per-endpoint circuit breaker and the attempt records an operator reads back when a receiver later disputes whether it was told.

The contract is at least once, never exactly once. A crash or a lost response can still cause a resend, so the platform hands over a message identifier your receiver checks before it acts. Delivery owns getting the notice there. Deduplicating it is the one piece of the work only your receiver can do.

Receiving, verifying and answering

Each delivery reaches your receiver as one POST request with a JSON body. Three of its headers follow Standard Webhooks, an open scheme with verification libraries in most languages: webhook-id identifies the message, webhook-signature holds the signature or signatures, and webhook-timestamp gives the signing time in whole seconds. The platform adds webhook-delivery-id for the individual attempt and webhook-endpoint-id for the endpoint it addressed.

Verify with a Standard Webhooks library rather than by hand, using the endpoint's signing secret, and let the library reject a timestamp outside its tolerance. The library accepts a five-minute spread between that timestamp and your clock, so keep the receiver's clock synchronised. The platform re-signs an envelope before an attempt once its timestamp has reached four minutes old, so a message that waited in backoff does not arrive already expired. While a signing secret rotates, webhook-signature holds one signature per secret, separated by a space, and verifying against any one of them is enough.

A duplicate reaches you in two situations. The first is a retry of a delivery whose outcome the platform never confirmed. The second is a receiver that did the work but did not reply inside the twenty-second attempt window, so the platform retries a request you already fulfilled. A retry repeats the same webhook-id, while webhook-delivery-id changes on every attempt. Record the identifiers you have handled and return success for one you recognise. Store the handled identifier and the resulting business change atomically, or use an equivalent idempotent design.

A replayed event is the exception. Replay gives each event a new webhook-id and marks it as a replay, so deduplicate a replay on the identifiers inside its payload.

Your reply
What happens next
Send it when
Any 2xx
Delivery ends and is marked delivered. An endpoint that requires receipts also needs the receipt in time.
You received the message and are done with it.
A 4xx other than 408 or 429
Stops at once. Nothing retries.
You never accept this exact message.
408 or 429
Retries.
Rate-limiting or not ready. The platform treats it as a retryable client failure.
A 5xx
Retries.
Something broke on your side.
No reply inside twenty seconds
Counted as a failure and retried.
Never on purpose. Acknowledge first, do the work after.

When an attempt fails, the platform records what kind of failure it hit:

Failure kind
What it means
Retried
DNS failure
The endpoint's host did not resolve.
Yes
TLS failure
The security handshake broke.
Yes
Connection timeout
The connection never opened.
Yes
Read timeout
The connection opened but no reply arrived.
Yes
Invalid response
The reply carried no recognisable HTTP status, or was a redirect.
Yes
A 5xx, 408 or 429 reply
The receiver failed or asked the platform to wait.
Yes
Rejected by the receiver
Any other 4xx.
No
Receipt body hash mismatch
A counter-signed receipt verified but names a different body.
No
Refused address, recorded as TARGET_REFUSED
The platform's destination policy refused the address. Nothing was sent.
No

Sending the identical request again would reproduce the identical refusal, so the platform does not retry a terminal failure. The failure kind tells an operator whose problem it is, and whether the platform is still trying, before anyone opens a log.

An endpoint can require counter-signed receipts. A 2xx reply only shows that the platform reached you and you answered. A counter-signed receipt goes further: your receiver signs the delivery identifiers and a hash of the exact body with the endpoint's signing secret. The dalp.whr.v1 scheme ties the receipt to that delivery and payload. The receipt contract defines the signing string, request fields and deadline. A missing receipt causes a retry. A receipt whose signature does not verify is refused without changing the delivery.

Registering an endpoint

An organisation Owner or a Platform administrator registers and manages endpoints on the Console's Webhooks page, which also shows each endpoint's breaker state and every event's attempts. The signing secret appears once, when the endpoint is created or its secret is rolled. Rolling keeps the previous secret valid for 24 hours, and the platform signs each delivery with both secrets during that overlap.

Thin and fat payloads

A thin payload leaves out personal data, and every endpoint starts thin. A fat payload includes the personally identifying fields that the endpoint's subscriptions expose. The platform computes that list of fields from the subscriptions and refuses the switch to fat until the endpoint owner acknowledges every field on it. An endpoint cannot be created fat: register it thin, then move it to fat once the fields it would expose are known.

Address checks

Registering or updating an endpoint checks the address first. The URL must use HTTPS and resolve only to a public address, so a private, loopback or link-local target is refused before it is saved. Register the address your receiver is reachable at from the public internet.

At send time the platform resolves the endpoint's host, validates the address it got back and connects to that address directly, so no second name lookup can redirect the request after the check passed. Where a deployment allows specific internal addresses, those addresses lose this protection. The original host name still travels in the Host header and the security handshake, so virtual hosting and certificate checks behave normally.

The platform follows no redirects. A redirect reply is recorded as an invalid response and retried. Register the address the redirect points to. The platform reads at most eight kilobytes of a reply and stores that on the attempt record.

Recovery reuses recorded attempts

Sending a webhook and recording its outcome are separate effects. A process can stop after your receiver got the POST but before the attempt record is written, and recovery may repeat that send.

On recovery, the platform uses the endpoint, event and attempt number to look for an existing delivery record. When the record exists, recovery reuses its outcome. When it does not, recovery sends the request. This reduces repeated sends but does not deliver exactly once.

Once retrying stops, an Owner or Platform administrator can still send the event again. “Resend” on a failed event adds one attempt to the same event with the same webhook-id, and only the latest attempt of an event can be resent. “Replay events” sends events the platform already recorded for the endpoint, by block range or one at a time, each with a new webhook-id. The platform refuses a replay while the endpoint is switched off or its circuit breaker is open.

The circuit breaker that watches endpoint health

Webhook Delivery gives every registered endpoint its own circuit breaker, with three states: closed, open and half-open. It keeps one failing endpoint from consuming delivery capacity meant for every other receiver, and it lets recovery happen without an operator noticing and re-enabling the endpoint.

Thirty consecutive failed deliveries open the breaker, which pauses live sends to that endpoint. The platform then probes the endpoint at most once a minute. Two successful probes in a row move the breaker to half-open, and live deliveries resume. One further successful delivery closes the breaker and resets the failure count to zero, while one failed live delivery sends it back to open. The platform records each state change as it happens.

Recovery is automatic, and a failed delivery can send it back to open.An endpoint's circuit breaker holds one of three states: closed, open, or half-open. Closed delivers normally and resets the failure count on every success. Thirty consecutive failed deliveries move it to open, which pauses live sends to that endpoint. While open, the platform runs its own background probe on a fixed schedule the endpoint owner does not control, and two of those probes succeeding in a row moves the breaker to half-open. Live deliveries resume at half-open: one further successful delivery closes the breaker and resets the failure count to zero, while a single failed live delivery sends it back to open. A manually triggered test event is excluded from every step of this cycle, so probing an endpoint by hand can never open, advance, or close its breaker.Recovery is automatic, and a failed delivery can send it back to open.The same figure as the desktop variant, drawn portrait for phone widths. Closed delivers normally and resets its failure count on every success. Thirty consecutive failures move it to open, which pauses live sends and starts the platform's own background probe on a fixed schedule. Two of those probes succeeding in a row moves it to half-open, where live deliveries resume. One further successful delivery closes the breaker completely. One failed live delivery sends it back to open. A manually triggered test event never advances this cycle.

The platform probes an open circuit breaker, and successful probes admit live traffic through half-open before normal delivery resumes.

Test events stay out of that accounting. An operator can send a test event to a disabled or recovering endpoint. It moves neither the breaker nor the failure count, and it neither replaces nor speeds up the automatic probes.

Evidence that the notice went out

Webhook Delivery records every attempt it makes toward your receiver, not only the one that finally succeeds. Every attempt leaves a row with the attempt number, the status your receiver returned, its response headers and the first part of its body. A failed attempt's row also records the failure kind and the trace identifier that ties it to the wider operation. The platform keeps these records for 30 days after a delivery settles, so export what your dispute and audit policy needs before then.

Delivery also adds an entry to the event's chain of hop hashes, with a hash of the exact payload sent and the endpoint it was signed for. That records which content went out to which endpoint, which matters when a receiver disputes what it received.

The delivery signature and the counter-signed receipt both use a secret that the platform and your receiver share. Each side can check the other, but neither signature proves to a third party which side produced it.

When retrying stops, the platform writes a warning in its logs and marks the operation failed in its traces. Both name the attempt number, the failure kind and the event's age, so an operations team finds abandoned events there.

The platform versions its signing secrets. A successful delivery updates the last-used time of each secret version that signed it, and the endpoint's secret.lastUsedAt reports the most recent of those times.

Frequently asked questions

Can a private-network deployment point a webhook at an internal address?

Not from the endpoint form. The public-address check applies to every organisation the same way. A private-network installation can allow specific internal destinations at the deployment level. The deployment operator makes that decision for the whole installation, and an organisation cannot make it by editing its own endpoint URL. If a legitimate internal address keeps being refused, ask the operator about that allowance.

What happens to deliveries already in flight when I change an endpoint's URL?

Nothing in flight retargets silently. If the endpoint still has pending deliveries, a plain URL change is refused. You either wait for those deliveries to finish, or resend the update with an explicit acknowledgement that pending attempts retry against the new address. A completed attempt keeps its original destination. Each attempt reads the endpoint's current URL at send time, so a delivery still retrying when you switch lands on the new address from its next attempt onward.

Was this page helpful?