Delivery webhooks
Instead of polling GET /v1/orders/{id}, pass a webhook_url (https only) when creating an order. paperplane POSTs a signed event on each status transition.
Events
Fired on the same transitions the email notifications cover:
submitted
refused
held
mailed
delivered
failed
Request
Every event is a POST with a Webhook-Id header (for idempotency) and this body:
version is the payload schema version, currently always 1. It only
changes if data’s shape changes in a way your parser could break on (a
field renamed or removed); we’ll bump it and note the change here when that
happens — adding a new optional field does not bump it.
Verify it came from us
Set OUTBOUND_WEBHOOK_SECRET on the server. Every request carries:
where v1 = HMAC-SHA256(secret, t + "." + rawBody). Reject events older than a few minutes (replay window). If the secret is unset, paperplane refuses to send unsigned events — you can always prove a status push came from us.
A runnable Node example that verifies this exact scheme against a real
payload shape (no dependencies, no network) lives at
scripts/verify-webhook-example.mjs
in the repo:
It signs a sample event with a throwaway secret, verifies it the same way
your endpoint should, then shows a tampered body and a stale timestamp both
failing verification — copy the verify() function straight into your
handler.
Delivery
Fire-and-forget with one retry after a short backoff. If your endpoint is down, the reconcile cron re-converges order state and re-fires on the next poll.Last modified on September 17, 2026