Flow signs every delivery with a secret only you and Flow know, so your system can check each one is genuine and unchanged before acting on it. It takes a few lines of code, and the same lines work in any language.
In review: written from the product, not yet walked through live.
1Keep the signing secret safe
In Settings → Developers → Outbound Webhooks — Flow → another app (1) — each webhook you create with + New webhook (2) gets a signing secret, shown once. Your webhooks are listed below (3); their addresses work like passwords, so they’re blurred here. Store the secret where your system keeps secrets — for example a setting called
FLOW_WEBHOOK_SECRET — never in your code. If you lose it, rotate it (step 5).
2Read the headers on each delivery
Every delivery arrives as a POST with these headers:
X-Webhook-Signature— the signature to check.X-Webhook-Signature-Previous— the same, made with your old secret, for 24 hours after you rotate.X-Flow-Event— what happened, such asmessage.received.X-Flow-Delivery— this delivery’s id. A retry of the same delivery keeps the same id.
3Compute the signature from the raw body
The signature is an HMAC-SHA256 of the exact bytes Flow sent, keyed with your secret, written as hex. Compute it from the raw body before your framework parses the JSON — parsing and re-writing it changes the bytes, and the check fails. Then compare the two in constant time, never with
==.Node.js
// Node.js — rawBody is a Buffer of the request body, sig the X-Webhook-Signature header
const crypto = require('crypto');
const expected = crypto
.createHmac('sha256', process.env.FLOW_WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
// timingSafeEqual throws when lengths differ — check first
const ok = sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));Python
# Python — raw_body is bytes, sig the X-Webhook-Signature header
import hashlib, hmac, os
expected = hmac.new(os.environ["FLOW_WEBHOOK_SECRET"].encode(), raw_body,
hashlib.sha256).hexdigest()
ok = hmac.compare_digest(expected, sig)PHP
<?php // PHP
$rawBody = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $rawBody, getenv('FLOW_WEBHOOK_SECRET'));
$ok = hash_equals($expected, $sig);4See it pass — and fail
Here is the Python check running on a signed sample. The right signature passes (1). A wrong signature fails (2) — and so does a body that changed by a single word on the way (3), which is the point: a tampered message never looks genuine. Answer anything that fails with
401 and do nothing else.
5Rotate the secret without missing anything
Open the webhook and click Rotate secret. Flow shows the new secret once, and for the next 24 hours sends both signatures:
X-Webhook-Signature with the new secret and X-Webhook-Signature-Previous with the old one. Accept a match on either while you switch your system to the new secret; after 24 hours only the new one is sent.6Ignore repeats, answer fast
If your system doesn’t answer with a
2xx within 2 seconds, Flow sends the same delivery again — after 30 seconds, 5 minutes, 30 minutes and 2 hours. Save each X-Flow-Delivery id you’ve handled and skip ones you’ve seen, so a retry never runs twice. Reply 200 first, then do slow work in the background.© 2026 SkedX · Sample data only · Share and link freely · Copying the product or its design is not allowed · Terms
Common questions
My check always fails. What’s wrong?
Almost always the body: your framework parsed the JSON and you signed the re-written text. Sign the raw bytes as received. Also check the secret has no extra space or line break, and that you compare against the header’s exact value.
Is the signature sent as sha256=…?
No —
X-Webhook-Signature is the bare hex digest. (Inbound hooks are the other way round: there, your app sends X-Flow-Signature: sha256=… to Flow.)Can someone replay an old, genuine delivery at me?
The signature proves Flow sent those exact bytes, not when. Keep the
X-Flow-Delivery ids you’ve handled and skip repeats — that stops both Flow’s own retries and a replayed copy from running twice.What happens if my endpoint is down for a while?
Flow retries each delivery five times in all over about two and a half hours, then keeps it in the webhook’s delivery log, where Send again replays it. See Get notified with outbound webhooks.
Next in APIs & DevelopersConnect Flow to Zapier →
Useful links
- Outbound Webhooks in Flow — Settings → Developers → Outbound Webhooks
- Flow API reference — every header and event, with examples
Previous: Get notified with outbound webhooks · All APIs & Developers guides
Did this guide help?
Still stuck?Chat with support →

