Webhooks
The one outbound callback system — the event vocabulary, how to verify the HMAC signature, the retry schedule and manual redelivery, and how a single endpoint carries both delivery events and inbound mail.
A webhook endpoint is an HTTPS URL your Org registers, with a signing secret and
a set of subscribed event types. It’s the single outbound-callback mechanism of the
product: one endpoint carries delivery events for the mail you send and
inbound mail as email.received. There is no second delivery path — an inbound
route delivers to a webhook endpoint like everything else.
Early access. Email is being rolled out behind an Org flag. If you don’t see it in the Console, it isn’t enabled for your organization yet.
Register an endpoint
Create an endpoint under Email → Webhooks, or with the CLI, choosing which events it should receive:
lbr email webhook create https://app.example.com/hooks/email \
--event email.delivered --event email.bounced --event email.received
Creation returns a signing secret (whsec_…) — shown once. Store it; you’ll
need it to verify every delivery. Lost it? Rotate for a new one (below). The URL
must be HTTPS.
The event vocabulary
Five event types travel over the same mechanism. Subscribe an endpoint to any combination.
| Event | Fires when |
|---|---|
email.sent | A message you sent is handed off to the mailbox provider. |
email.delivered | The provider accepted it for the recipient. |
email.bounced | Delivery failed. A permanent bounce also suppresses the address. |
email.complained | The recipient marked it as spam; the address is suppressed. |
email.received | Inbound mail matched an inbound route pointing at this endpoint. |
failed is a message state (retries exhausted or a permanent provider error), not
an event — there is no email.failed webhook.
Delivery-event payload
Delivery events share a compact shape — the event type, the message id, and when it occurred:
{
"type": "email.delivered",
"messageId": "msg_7h3k9m2p",
"occurredAt": "2026-07-06T09:15:04Z"
}
Look the message up in the message log by its messageId for the
full detail (recipients, subject, the delivery events so far). The email.received
payload is richer — it carries the parsed inbound message; see
Receiving mail for its shape.
Verify the signature
Every delivery is signed so you can prove it came from Longbridge and hasn’t been tampered with or replayed. Three headers accompany the POST:
Longbridge-Webhook-Id: <delivery id>
Longbridge-Webhook-Timestamp: <unix seconds>
Longbridge-Webhook-Signature: v1=<hex>
The signature is an HMAC-SHA256 over the timestamp, a literal ., and the raw
request body, keyed by your endpoint’s signing secret:
v1 = hex( HMAC_SHA256( secret, timestamp + "." + raw_body ) )
To verify: recompute the HMAC over the received timestamp and the raw body (the
exact bytes — don’t re-serialize parsed JSON), and compare it to the value after
v1= using a constant-time comparison. Reject the request if it doesn’t match, and
reject stale timestamps (for example, older than five minutes) to blunt replays.
import hmac, hashlib
def verify(secret: bytes, headers, raw_body: bytes) -> bool:
ts = headers["Longbridge-Webhook-Timestamp"]
sig = headers["Longbridge-Webhook-Signature"] # "v1=<hex>"
signed = ts.encode() + b"." + raw_body
expected = "v1=" + hmac.new(secret, signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
Return any 2xx to acknowledge a delivery. Anything else — or a timeout — counts as
a failure and is retried.
Retries
A delivery that doesn’t get a 2xx is retried on a fixed, backing-off schedule,
seven attempts in all before it’s marked failed:
30s → 2m → 10m → 1h → 4h → 12h
That spans roughly 17.5 hours from the first attempt, which is plenty of room to
survive a short outage on your side. Make your handler idempotent — a delivery
may arrive more than once, and the Longbridge-Webhook-Id header is a stable
de-duplication key for a given delivery.
Redeliver by hand
You can re-send a delivery from its frozen payload — after fixing a bug in your handler, say, or backfilling events you missed while your endpoint was down. Find recent deliveries for an endpoint and redeliver one:
lbr email webhook deliveries <endpoint-id>
lbr email webhook redeliver <delivery-id>
Redelivery inserts a new delivery from the original payload (a clean audit trail, not a mutated retry) and re-signs it with the current secret. It’s bounded by the 30-day content-retention window.
Rotate the signing secret
Rotate the secret if it leaks, or on a schedule:
lbr email webhook rotate-secret <endpoint-id>
The new whsec_… is returned once, and the old secret stops signing
immediately — so deploy the new secret to your verifier before you rotate, or
briefly accept either during the switchover.
Pause without deleting
To stop deliveries to an endpoint without losing its configuration, disable it:
lbr email webhook update <endpoint-id> --disabled
Re-enable it with --enabled. A disabled endpoint receives nothing while it’s off.
Note you can’t delete an endpoint that an inbound route still
targets — repoint or remove the route first.
Where to next
- Receiving mail — the
email.receivedpayload and routing. - Sending mail — the delivery events these carry.
- Suppression — what bounces and complaints trigger.