Webhooks
How an automation's webhook step calls your system, signs each request and tries again when it fails.
An automation's Webhook step sends an HTTP request to your system for everyone who reaches
it: a POST, PUT or PATCH to a public https:// address, with the headers you set and a JSON
body.
The body
By default the body is the profile and the event that started the automation (an automation that starts from a custom field or a date has no event: the body is the profile):
{
"profile": { "id": "0192f3a4-…", "email": "jane@acme.com", "first_name": "Jane", "loyalty_tier": "Gold" },
"event": { "order_id": "1001", "total": { "amount": 4900, "currency": "EUR" } }
}Or write your own JSON with Liquid over profile and event. Fill in values with the json
filter, which adds the quotes text needs and writes null for a value that isn't there:
{ "email": {{ profile.email | json }}, "order": {{ event.order_id | json }} }Checking the signature
Every request carries Postforge-Signature: t={time},v1={signature}: the Unix time it was sent and
an HMAC-SHA256, in hex, of {time}.{body} with your workspace's signing secret (Developers →
Webhooks in the dashboard). Check it against the raw body, and refuse requests more than five
minutes old:
import crypto from 'node:crypto'
function verify(header, body, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((part) => part.split('=')))
const expected = crypto.createHmac('sha256', secret).update(`${t}.${body}`).digest('hex')
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
}Header values marked secret in the step (an API key your system asks for) are stored encrypted and never shown again.
Answers and retries
- A
2xxanswer is a success. The run goes on. - No answer within 10 seconds, a refused connection,
408,429or a5xxis tried again: 5 tries in all, after 1 minute, 5 minutes, 30 minutes and 2 hours. - Any other answer (a
4xx, a redirect) won't get better and isn't tried again. - When it fails for good, the step's setting decides: the person goes on to the next step, or leaves the automation.
Postforge-Webhook-Id is the same on every try of one request, so you can recognize a request
that arrives twice.
The request log
Every request a webhook step decided is on the person's timeline: Webhook Sent when it got a
2xx, Webhook Failed after its last try (or at once when the answer won't get better). Each
holds the method, address, body and every try, never the headers. Developers → Webhooks in the
dashboard lists them, sent and failed, newest first.