Skip to content

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.

EventFires when
email.sentA message you sent is handed off to the mailbox provider.
email.deliveredThe provider accepted it for the recipient.
email.bouncedDelivery failed. A permanent bounce also suppresses the address.
email.complainedThe recipient marked it as spam; the address is suppressed.
email.receivedInbound 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