Signing secret
Each webhook endpoint has its own signing secret (a 48-character hex string). You receive it once, in the response toPOST /webhooks:
Rotating the secret
POST /webhooks/{id}/rotate-secret replaces the endpoint’s signing secret and
returns the new one once:
How Pocketsflow signs a request
For each delivery, Pocketsflow:- Builds the JSON body once: the envelope fields
id,typeandcreated, then your event data, thenwebhookId. - Signs the exact bytes of that body — two ways, described below.
- Sends those same bytes as the request body.
The envelope fields in the body:
Verifying the signature (V2, recommended)
X-Pocketsflow-Signature-V2 binds a timestamp to the body, so a captured
request can’t be replayed later with a fresh timestamp.
1
Read the raw body
Capture the request body as raw bytes before any JSON parsing or
middleware re-serializes it. In Express, use
express.raw({ type: "application/json" }) for the webhook route.2
Parse the header
Split
X-Pocketsflow-Signature-V2 on , into t=<seconds> and
v1=<hex>.3
Check the timestamp
Reject the request if
t is more than 5 minutes away from your
current time.4
Recompute the HMAC
Compute
HMAC-SHA256(secret, t + "." + rawBody) and hex-encode it.5
Compare in constant time
Compare your digest to
v1 with a timing-safe comparison
(crypto.timingSafeEqual, hmac.compare_digest, hash_equals). Reject
with 400 if they differ.6
Deduplicate, then process
Skip the event if you’ve already handled its
X-Pocketsflow-Event-Id
(also in the body as id). Otherwise record the id, parse the JSON, route
on type, and process.Node.js
Python
Deduplicating events
Pocketsflow retries failed deliveries (see Consuming webhooks) and you can redeliver an event from the delivery log, so the same event can reach you more than once. Every copy carries the same event id inX-Pocketsflow-Event-Id and the body’s id. Store the ids you’ve processed
(keeping them for at least a few days is plenty) and skip any id you’ve already
seen. Combined with the 5-minute timestamp tolerance, this also blocks replayed
requests.
Legacy signature (V1)
X-Pocketsflow-Signature is still sent on every delivery so existing
integrations keep working. It is the hex HMAC-SHA256(secret, rawBody) of the
raw body only:
Node.js
V1 does not cover the timestamp, so on its own it can’t stop a captured
request from being replayed. If you stay on V1, deduplicate on the event id
and treat
X-Pocketsflow-Timestamp as a secondary freshness check only.
New integrations should verify V2.Additional defenses
- Serve your endpoint over HTTPS with a valid certificate (Pocketsflow only
delivers to
https://URLs on public addresses; it follows up to 3307/308redirects, re-checking each hop the same way, and any other3xxcounts as a failed delivery). - Validate
Content-Type: application/jsonand the payload shape before acting. - Respond fast — deliveries time out after 5 seconds. Acknowledge with a
2xxand offload work to a queue. - Restrict inbound access with a firewall/WAF if feasible, and never expose sensitive internal services directly.
Handling secrets safely
- Store secrets in environment variables or a secret manager — never in source control or client-side code.
- Use a distinct endpoint (and therefore secret) per environment; keep test-mode and live-mode endpoints separate.
- Rotate a secret with
POST /webhooks/{id}/rotate-secretif you suspect exposure or change infrastructure ownership.