Skip to content

Webhook steps in workflows

A workflow’s Send webhook step makes an HTTP request to your system when a workflow runs. This page covers what it sends, how to receive it safely, and what you see when something goes wrong. For webhooks that tell your systems about events in a workspace (a response was completed), see Webhooks and Events; the two share their signature scheme.

You choose the method (GET, POST, PUT, PATCH or DELETE), the URL, headers, query parameters and a body. Any of them can include merge tokens such as {{trigger.response.email}}, which are filled in from the run when the step executes.

On Team and above you can also set authentication (an API key, a bearer token, basic authentication or OAuth2 client credentials), build the body from fields, and use Run Test to send a sample request while you build the step.

A request waits up to 30 seconds for an answer and follows up to five redirects. We only send to public addresses; a URL that points at a private network or an internal host is refused.

Each step has a Delivery setting (Team and above):

Wait for the response Send in the background
What the step does Sends the request and waits for the answer Hands the request to our delivery queue and finishes at once
Later steps can use the response Yes No
If your endpoint is down Waits up to 5 minutes, then the step fails Keeps trying for about 3.7 days, on the same schedule as webhooks
A request we sent and got no answer to Never sent again Sent again on the schedule
When it gives up The run shows the step as failed The workflow’s failure notification, or an email to the workspace’s owners and admins
Where you see it Run history Run history shows the delivery’s state: queued, retrying, delivered (attempt 3), or failed after 9 attempts

A step that waits is the mode for anything a later step needs the answer to. A step in the background is the mode for telling another system something happened and making sure it arrives. The editor starts a new step in the background and switches it to waiting if a later step uses its response; it won’t let you save the background mode on a step whose response a later step reads.

Because the background mode can send a request more than once, your endpoint should be ready for that. That is what the next section is for.

Every request from a webhook step carries an Idempotency-Key header: 32 hexadecimal characters that identify this execution of the step.

  • It is the same on every attempt of the step, including retries in the background.
  • It is the same when you replay the step or send a delivery again, so a replay reaches your endpoint as the same request.
  • It is different for each run, and for each pass of a loop.

Store the key when you handle a request, and ignore a request whose key you’ve already handled. That turns “at least once” into “once”. You can turn the header off for a step in its Delivery section if your endpoint rejects unknown headers; if you set a header called Idempotency-Key yourself, yours is the one we send.

Turn on Signing in the step to sign its requests. We add three headers, the same as on a webhook:

Header Meaning
webhook-id The step’s key (the same value as Idempotency-Key), identical on every attempt.
webhook-timestamp When this attempt was built, in Unix seconds. A retry days later is signed afresh.
webhook-signature v1, and a base64 HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body}. Two signatures, space-separated, for 24 hours after you replace the secret.

The signing secret is made when you turn signing on and is shown once; copy it then. Replace the secret makes a new one and keeps the old one signing alongside it for 24 hours so you can switch over without a gap. Verification is identical to webhooks: the samples for Node.js, Python and PHP work unchanged. Use the raw body, check the timestamp, and accept any listed signature.

If signing is on and a step has no usable secret, the step fails rather than sending an unsigned request.

When a step waits, later steps can read what came back, for example {{steps.lookup.webhook_result.status_code}}, where lookup is the step’s id:

Field Value
webhook_result.status_code The HTTP status.
webhook_result.body The response body, as text, up to 2 MB (webhook_result.body_truncated says if it was cut).
webhook_result.body_json The same answer as data, when it is a complete JSON object or array of up to 256 KB: {{steps.lookup.webhook_result.body_json.customer.id}}, or body_json.items[0].sku for a list. Empty when the answer is not JSON, was cut, or is larger.
webhook_result.headers The response headers.
webhook_result.is_success Whether the answer was a 2xx.
webhook_result.duration_ms How long the request took.

The variable picker in the editor lists these for you. A step that is sent in the background has no response to read.

The failed step in run history says what happened and what to do next, in words:

  • The destination couldn’t be reached for 5 minutes. Nothing was sent, so nothing was processed. Check the address and that the server is up, then replay the step.
  • The request was sent and no answer came back, or the answer was a server error (5xx). We can’t tell whether your system acted on it, so a waiting step doesn’t send it again. Check your system first; if it didn’t act, replay the step. The replay carries the same Idempotency-Key.
  • Your system refused the request (4xx), or answered with something that isn’t a success, such as a redirect we couldn’t follow to the end. Fix the address, credentials or body in the step.
  • The address isn’t one we send to. Use a public address.

Replay step (Growth and above) runs a failed step again with the data it had the first time, including everything it read from earlier steps. Send again (Team and above) queues another delivery of a background step. Both rely on the run’s data, which is kept for a limited time (7 days on the smallest plans, 30 days and more above); after that the request can no longer be rebuilt.

A workflow’s failure notifications (Team and above) can be set to dead letter only, which tells you about steps we gave up on and stays quiet about the rest.