Events
The last four are in the catalogue so you can subscribe to them now without a
breaking change later; nothing raises them yet, and this page will say so until
something does.
Registering an endpoint
Settings → Webhooks in the dashboard. Give it an HTTPS URL and tick the events you want. You get a signing secret beginningwhsec_, shown once. Unlike an API key it
is stored intact — we need it to sign — but it is still only displayed at
creation.
Your URL must be HTTPS and must resolve to a public address. Endpoints
resolving to loopback, private ranges, or cloud metadata addresses are refused
on every attempt, checked at delivery time rather than only at registration, so
a name repointed later is caught too.
The envelope
Every delivery is aPOST with this body:
id is the delivery id. It is the same across every retry of that
delivery, which is what makes it usable for deduplication — see below.
from and envelope_from are different things and both are given.
from is the header a person sees; envelope_from is the address that
actually delivered and is where a bounce would go. They differ for mailing
lists and forwarders, and code that checks only one will eventually be
surprised.There is no IMAP uid in message.received. The event fires the moment the
mail server accepts the message, which is before the mailbox has assigned one.
Use message_id, which is the sender’s and never changes.Bounce payloads
permanent: true means stop sending to that address. It is surfaced as its
own boolean so you do not have to learn AWS’s vocabulary to act on the one
distinction that matters. Continuing to send to addresses that hard-bounce is
the fastest way to lose a domain’s sending reputation.
Verifying the signature
Two headers arrive with every delivery:
The signed string is
${timestamp}.${rawBody}.
Node
Responding
Answer2xx quickly. Anything else — including a 3xx — counts as a
failure and is retried. We do not follow redirects, so a redirecting endpoint
never receives anything.
We wait 10 seconds. If your handler needs longer, acknowledge immediately
and do the work afterwards; that is what the retry schedule assumes.
Retries
Six attempts, then we stop:
About half a day in total — long enough that an endpoint broken over lunch
still receives its events once fixed, short enough that a permanently dead one
stops costing anything within a day.
Disabling an endpoint stops its queued deliveries; they are not retried at you
later.
Deduplicate on id
Deliveries are at-least-once. A network failure after your server processed the
request but before we saw the response is indistinguishable, from our side,
from one that never arrived — so we retry, and you may see the same id twice.
Store the id and ignore one you have already handled. We also deduplicate on
our side, so a message the mail server retried does not become two separate
events, but that protects against a different failure than this one.
Ordering
Events are not ordered. Deliveries run in parallel so one slow endpoint does not delay everybody else’s events, and a retried event arrives after events raised later. UsecreatedAt if order matters to you.