Core responsibilities
Your endpoint should:- Receive the HTTP
POSTfrom Pocketsflow. - Verify the
X-Pocketsflow-Signature-V2against the raw body, within a 5-minute tolerance (see Authentication & security). - Deduplicate on the event id (
X-Pocketsflow-Event-Id, also the body’sid). - Acknowledge with a
2xximmediately. - Parse the JSON and route on the
X-Pocketsflow-Eventheader (or the body’stype). - Process the event idempotently in the background.
Delivery guarantees
Each attempt has a 5-second timeout. If your endpoint answers
5xx or
429, or can’t be reached at all (connection refused, DNS failure),
Pocketsflow retries up to 3 attempts in total, about 1 second and then 4
seconds apart. A timeout or reset is not retried — your endpoint may
already have processed the event. 307/308 redirects are followed (up to
3 hops, each re-checked); other answers — 2xx (success), other 3xx, or
any other 4xx — are final. Every attempt is recorded in the delivery log
(GET /delivered-webhooks), and you can re-send any logged delivery with
POST /delivered-webhooks/{id}/redeliver. Retries are best-effort, not a
durable queue: design for an event occasionally arriving more than once —
or not at all.- Acknowledge fast. Verify the signature, enqueue the event, and return
200— all well under 5 seconds. Never do slow work (emails, third-party calls, heavy DB writes) before responding. - Reconcile. Periodically pull the source of truth from the API so a missed
delivery self-heals:
- Orders →
GET /orders - Payments (one-time + subscription) →
GET /payments - Subscribers →
GET /subscriptions/subscribers
- Orders →
Idempotency
Handlers must be safe to run more than once for the same logical event. The first line of defense is the event id: a retry or a redelivery of an event carries the sameX-Pocketsflow-Event-Id (and body id) as the
original, so record each id you process and skip ones you’ve seen.
The event id is per delivery of an event, though — reconciliation via the API
or two separate events about the same object won’t share it. For business-level
idempotency, also key on a stable value from the payload:
Before processing, check whether you’ve already handled that key; if so, skip. If
not, record it and proceed. This protects you from re-processing during
reconciliation and from any duplicate delivery.
Ordering
Events are not guaranteed to arrive in the order they occurred. Don’t assume, for example, thatpayment_intent.succeeded always lands before the first
invoice.payment_succeeded. Make each handler tolerant of out-of-order arrival
(upsert state; use the payload’s own fields and the API rather than relying on
sequence).
Delivery log and redelivery
Every attempt is logged with its HTTPstatus, durationMs, a short error
(for example Timed out, Connection refused, HTTP 503,
Blocked destination), the attempt number, the eventId, and the exact body
that was sent. Logs are kept for 30 days.
GET /delivered-webhooks?page=1&pageSize=50&webhookId=…&event=order.completedlists attempts, newest first (pageSizemax 100), with apaginationblock.GET /delivered-webhooks/{id}returns one attempt.POST /delivered-webhooks/{id}/redeliverre-sends that attempt’s body to its endpoint with fresh signature headers (same event id) and logs the result as a new attempt.
Error handling
Plan for invalid payloads, downstream outages, and transient connectivity issues:- Log every delivery with enough context to debug (event type, event id,
idempotency key, timestamp, and
webhookId). - Return
2xxonly after you’ve safely recorded the event for processing — not after the full downstream work completes. - Alert when your handler’s error rate crosses a threshold.
Recommended flow
1
Read the raw body
Capture raw bytes before parsing so signature verification is exact.
2
Verify the signature
Check the V2 timestamp is within 5 minutes, recompute the HMAC-SHA256 and
compare in constant time. Reject on mismatch.
3
Enqueue and acknowledge
Persist the raw event keyed by its event id (ignore an id you already have)
and return
200.4
Process asynchronously
A worker routes on
event.type, skips already-processed keys, performs the
action, and marks the key done.