Verifying an inbound webhook
An inbound webhook arrives with no session, so it has to prove itself four ways before anything happens: that it came from the provider it claims, that it is recent, that this exact delivery has not already been handled, and that it maps to something Connect serves. A delivery that fails any of the four is refused whole. Nothing partial is written, and the provider is free to retry.
Why a callback is a different kind of request#
Every other request into Connect carries a session, and the session decides the workspace. A provider callback carries neither. It is an unauthenticated POST from the public internet that claims a carrier or a mail provider has something to report, and by the time it arrives the event it describes has already happened somewhere else.
That combination is what makes the checks worth spelling out. A callback that is believed too easily can invent a call that never occurred, mark a message delivered that was not, or drive work in a workspace it has no relationship with. A callback that is believed too reluctantly loses real events, because providers retry for a while and then stop.
The four questions#
| Question | What it stops | When the answer is wrong |
|---|---|---|
| Did this come from the provider it claims? | Anyone who knows the address inventing events | Refused before the body is interpreted at all |
| Is it recent? | A captured delivery being presented long afterwards | Refused as stale, even though the signature is valid |
| Is this delivery new? | The same event being acted on more than once | Accepted and acknowledged, but nothing is done a second time |
| Does it map to something served here? | A guess at a workspace, a channel or a route | Refused rather than resolved to the nearest match |
The order matters. Origin is settled first, because interpreting a body you have not authenticated is how a parser becomes an attack surface. Freshness is second, because a valid signature on an old delivery is exactly what a replay looks like. Only then is the content read.
Replay and retry are not the same problem#
A replay is the same delivery presented again by somebody who should not have it. A retry is the same delivery presented again by the provider, usually because the first acknowledgement was lost. The first must be refused; the second must be accepted and must not produce a second action. One mechanism handles both: each delivery carries an identifier that is recorded once, and a second arrival of a recorded identifier is acknowledged without repeating the work.
This is why a retried event never doubles a call record, a message or a follow-up. Idempotency for retried telephony webhooks sets out the reasoning for the telephony case, where retries are common and the cost of acting twice is a customer being rung twice.
The one deliberate exception#
Almost everything in Connect resolves the workspace first and the record second. A mail provider's delivery webhook cannot: it identifies its mailbox before any workspace is known, so the scoped lookup that guards every other mailbox route is not available to it. It is the only caller that still resolves a mailbox from outside a workspace, it is written down as such, and webhook verification is what stands in for the missing scope.
The trade is visible rather than hidden, which is the point. See Proving a mailbox belongs to a workspace for the four routes that were closed and why this one was not.
What a refusal looks like from outside#
- The provider receives a refusal, not a redirect and not an error page.
- Nothing is created: no call, no message, no follow-up, no partial record that somebody has to clean up later.
- The refusal is counted. A run of them is an operational signal rather than silence, and it surfaces where line and mailbox health surface.
- No detail about why is returned to the caller. A refusal that explains itself is a tool for the next attempt.
If a provider reports failing deliveries and Connect shows no events, An inbound webhook was rejected walks through the four causes in the order they are worth checking.
Questions#
Does a rejected webhook mean the event is lost?
Usually not. Providers retry on failure for a period, so a transient cause such as a clock drift that has since corrected itself resolves on the retry. A cause that persists past the provider's retry window does lose the event, which is why repeated refusals are treated as operational rather than routine.
Is this the same signature scheme Connect uses on its own outbound webhooks?
No. Verifying what arrives and signing what leaves are separate concerns with separate secrets. Verifying a Connect webhook signature covers the outbound direction, which is the one an integrator implements.
Can a valid signature from one workspace's provider affect another workspace?
The fourth question is what prevents that. A delivery is resolved to the record it names within the scope that record belongs to; a delivery that resolves to nothing is refused rather than attached to the nearest plausible workspace.