Skip to main content
Register an HTTPS URL once via POST /v1/webhook-endpoints and we’ll deliver signed JSON for every event. No polling.

Event types

Post lifecycle

Tokens & versions

Subscription & billing

These carry billing state in data — see the shapes in WEBHOOK_EVENT_TYPES. This list is canonical — see WEBHOOK_EVENT_TYPES in packages/schemas/src/webhook-events.ts. Adding an event is non-breaking; removing one is breaking and shows up in the changelog.

Envelope

Every delivery has the same outer shape:
envelope.json
The data field is the only thing that varies between event types. The envelope is stable so consumers can write one verifier and one router.

Delivery headers

Every delivery includes:

Signature verification

The signature is HMAC-SHA256(secret, raw_body) — computed over the exact bytes of the request body, with no timestamp. The value is hex-encoded and prefixed with sha256=. Verify before parsing the body:
verify.ts
Compute the HMAC over the raw body before JSON-parsing it — reserializing changes bytes and breaks the digest. Reject the request if verification fails.

Idempotency

Delivery is at-least-once: a retried event arrives with the same X-Letmepost-Event-Id but a new X-Letmepost-Delivery-Id. Deduplicate on X-Letmepost-Event-Id and treat a repeat as a no-op. There is no timestamp on the signature, so there is no replay window to enforce — the signature and event-id dedup are the security boundary.

Retries

Delivery is retried with exponential backoff on 5xx responses and network errors (DNS, TCP, TLS, timeout). The budget is 8 attempts with backoff starting at 5s and doubling each time — roughly 5s, 10s, 20s, 40s, 80s, 160s, 320s, 640s, about 21 minutes total. After the final attempt the delivery lands in the failed set and surfaces in the dashboard’s webhook log. A 4xx response is treated as a permanent failure and is never retried — a 4xx means the consumer deliberately rejected the payload (bad signature config, missing route, auth failure), and retrying can’t fix a config error. Each attempt times out after 10 seconds. Respond 2xx quickly and defer your work to a background queue if it takes longer.

See also