Overview
Add a Webhook trigger in the workflow builder and set a secret. Use the webhook URL (shown in the app or built from your app base URL and path below) in the external service (e.g. Slack, Zapier).
URL pattern
New URL (preferred) — uses a rotatable routing token:
POST app/workflow/hook/:platform/:webhook_token
Full URL: https://hooks.flowfn.com/v1/app/workflow/hook/:platform/:webhook_token. Path parameters:
platform(e.g.slack,stripe,github,shopify,twilio,typeform, orother)webhook_token= the rotatable routing token shown in the workflow trigger panel. Click Rotate URL to mint a new one (the old URL stops working immediately; the signing secret is untouched, so subscribers using only the signature header keep verifying as before).
Legacy URL (still supported) — uses your app + workflow + trigger codes:
POST app/workflow/hook/:platform/:app/:workflow/:trigger
Existing webhook subscribers on this URL continue to work. New triggers default to surfacing the token URL.
Webhook secret
Each webhook trigger has a webhook_secret. Signature validation is provider-specific:
- slack uses
x-slack-signatureandx-slack-request-timestamp. - stripe uses
stripe-signature. - github uses
x-hub-signature-256. - gitlab uses
x-gitlab-token(secret token). - shopify uses
x-shopify-hmac-sha256. - other uses
x-signaturewith HMAC SHA-256 of the raw body.
The server validates the signature using the trigger's webhook_secret.
Calling from your own backend (platform = other)
For custom integrations, use the other platform — sign the raw body with HMAC SHA-256 (hex digest) and send it in the x-signature header.
curl:
curl -X POST 'https://hooks.flowfn.com/v1/app/workflow/hook/other/<app>/<workflow>/<trigger>' \
-H 'Content-Type: application/json' \
-H 'x-signature: <hmac sha256 hex of body>' \
-d '{"event":"user.created","user":{"id":"u_123"}}'
Node.js (sign + POST):
import crypto from 'node:crypto';
const SECRET = process.env.FLOWFN_WEBHOOK_SECRET;
const url = 'https://hooks.flowfn.com/v1/app/workflow/hook/other/<app>/<workflow>/<trigger>';
const body = JSON.stringify({ event: 'user.created', user: { id: 'u_123' } });
const signature = crypto.createHmac('sha256', SECRET).update(body).digest('hex');
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-signature': signature },
body,
});
Python (sign + POST):
import hmac, hashlib, json, os, urllib.request
SECRET = os.environ['FLOWFN_WEBHOOK_SECRET'].encode()
url = 'https://hooks.flowfn.com/v1/app/workflow/hook/other/<app>/<workflow>/<trigger>'
body = json.dumps({'event': 'user.created', 'user': {'id': 'u_123'}}, separators=(',', ':')).encode()
signature = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
req = urllib.request.Request(
url,
data=body,
method='POST',
headers={'Content-Type': 'application/json', 'x-signature': signature},
)
urllib.request.urlopen(req).read()
Request body
The request body is passed as trigger inputs to the workflow. Paste a representative payload into the trigger drawer's Sample payload editor — FlowFn derives mapping paths from it for downstream tasks.
Responses
- 200 OK –
{ "ok": true }. The workflow run is enqueued asynchronously. - 400 / 401 / 403 – Invalid signature, unknown app/workflow, or webhook not configured for this trigger.
- 429 – Per-trigger and per-client rate-limited. Honour
Retry-After.
Version behavior
Webhook runtime executions resolve to the published active version for the given app code + workflow code and trigger id.