Webhook Trigger
Give your automation an HTTP URL that external apps, forms, and backends can call. Each accepted request starts a run with the posted payload.
- Use it to
- Start a run when an external app, form, payment provider, or script sends an HTTP request
- Find it
- Tools → Start → Webhook
- Cost
- Free (0 Step credits to receive; downstream Steps use their normal rate)
- Limits
- 10 MiB max request body · 60 s max held response · 1 MiB max custom response
- Output
- {{ 1.body.<field> }}, {{ 1.headers.<name> }}, {{ 1.query.<param> }}, {{ 1.method }}
- Instead
- Scheduler (recurring time-based runs) or Form Trigger (hosted web form)
{
"customer_name": "Acme Corp",
"amount": 4500
}Typeform, Stripe, GitHub or your own script sends JSON to the URL.
Set up and test your webhook
1. Add the trigger
- Open your automation in the editor.
- Select Tools → Start → Webhook in the dock (or right-click the canvas).
- Click the Webhook Step to open its setup panel.
- Run
- Send the sample POST request above (or click Run step in Test & Debug)
- You should see
- The Executions tab records a Completed run, and {{ 1.body.amount }} resolves to 4500.
Reading what arrived
- Outputs method, path, query, headers, body, and bodyRaw.
- Read posted JSON or form fields under {{ 1.body.<field> }}.
- Use {{ 1.bodyRaw }} to verify vendor signatures in a Code Step.
- Emits the parsed body at the top level ({{ 1.<field> }}).
- Scalar payloads sit under {{ 1.value }}; {{ 1.$full_result }} returns the full output.
- Update downstream references when toggling Body only.
| Reference | What it reads (when Step 1 is the Webhook) |
|---|---|
{{ 1.body.<field> }} | Field from parsed JSON or form body (or {{ 1.<field> }} when Body only is on) |
{{ 1.headers.<name> }} | Lowercase request header (credential and signature headers read as [REDACTED]) |
{{ 1.query.<param> }} | URL query parameter (?source=stripe → {{ 1.query.source }}) |
{{ 1.method }} | HTTP method (POST, GET, PUT, etc.) |
{{ 1.path }} | Request path |
{{ 1.bodyRaw }} | Unparsed body string for verifying third-party signatures in a Code Step |
For {"customer_name": "Acme Corp", "amount": 4500, "currency": "USD"}, pick fields from Workflow data ({{ 1.body.customer_name }}, {{ 1.body.amount }}, {{ 1.body.currency }}).
Filtering allowed HTTP methods
Tools → Start → Webhook accepts POST, GET, PUT, PATCH, DELETE, and HEAD. To reject other methods with 405 Method Not Allowed, use HTTP → New webhook request from Apps and select the allowed HTTP methods.
Locking the URL to one sender
Treat the webhook URL as a secret. Requests are unauthenticated until you set Shared Secret / Signature verification:
| Field | Behavior |
|---|---|
| Shared secret | Optional ({{ $secret.WEBHOOK_SECRET }}). Rejects unsigned or mismatched requests with 401 before starting a run. |
| Signature header | HMAC-SHA256 digest of raw body in hex or base64 (optional sha256= prefix). Defaults to x-glow-signature; set X-Hub-Signature-256 etc. |
With Signature header left blank, only x-glow-signature is checked: a sender signing under another header name is rejected until you switch it to x-glow-signature or enter its header name in Signature header.
Node.js sender example
import crypto from "node:crypto";
const rawBody = JSON.stringify({ event: "order.paid", orderId: "ord_123" });
const signature = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
await fetch(process.env.GLOW_WEBHOOK_URL, {
method: "POST",
headers: {
"content-type": "application/json",
"x-glow-signature": `sha256=${signature}`,
},
body: rawBody,
});When Body only is off, Glow masks credential headers (authorization, proxy-authorization, cookie, set-cookie, x-api-key, x-glow-api-key) as [REDACTED]. Built-in signature headers (x-glow-signature, x-hub-signature, x-hub-signature-256) and any other header carrying the same signature (in hex or base64, with or without sha256=) are masked too. Vendor headers (stripe-signature, svix-*, x-slack-signature, x-shopify-hmac-sha256) stay readable for verification against bodyRaw in a Code editor Step.
Expand Poll execution results via API (optional) to poll results or pass an x-glow-correlation-id header.
Examples to copy
What to expect when it runs
What format to send
Send JSON (Content-Type: application/json) or form-encoded payloads. Other content types arrive as text.
Limits
| Limit | Value | When exceeded |
|---|---|---|
| Maximum request body size | 10 MiB (10,485,760 bytes) | Rejected before starting a run. Send a file URL or ID and fetch it downstream. |
Held response timeout (lastStep / respondToWebhookStep) | 60 seconds | Caller gets 504 {"error": "timeout"}; the run continues in the background. |
| Maximum custom response body | 1 MiB | Returns 202 {"received": true} in Immediately mode, or 500 {"error": "response_not_sendable"} when held. |
What the sender gets back
Set When to respond (responseMode) to reply immediately or wait for a Step result:
| Mode | Value | Behavior |
|---|---|---|
| Immediately (default) | "immediately" | Replies on acceptance using Response status code (200), Response Content-Type (application/json), and Response body ({"success": true}). |
| When the last step finishes | "lastStep" | Holds up to 60 s and returns the unwrapped output of the last passing Step. |
| Using a Respond to Webhook step | "respondToWebhookStep" | Holds up to 60 s until the first passing Respond to Webhook Step sends its status, headers, and body. |
In Immediately mode, returning an error status code does not cancel the run.
| Status & response | Meaning |
|---|---|
200 {"success": true} | Active Webhook in Immediately mode accepted the request (or HEAD probe). |
202 {"received": true} | Automation is in Draft or trigger is disabled; no run started. |
401 | Missing or invalid signature header for Shared secret. |
409 {"error": "duplicate"} | Held mode (lastStep / respondToWebhookStep) received a duplicate x-glow-correlation-id; no second run started. |
500 | Held mode failed ({"error": "workflow_failed"}), missed Respond to Webhook ({"error": "no_response"}), or exceeded 1 MiB ({"error": "response_not_sendable"}). |
503 with Retry-After | Temporary capacity or rate-limit refusal; retry after delay. |
504 {"error": "timeout"} | Held response exceeded 60 seconds; run continues in background. |
Duplicate deliveries
Each accepted request can start a new run. Prevent duplicates when senders retry:
- Held response modes: Send a unique
x-glow-correlation-idheader per event. Retries return409 {"error": "duplicate"}without starting a second run. - Immediately mode: Store event IDs in a Data Store (route lookups with Conditions) or serialize runs in Concurrency & queueing. Also pass an idempotency key to external write APIs.
What’s Next?
- Send custom status codes, headers, bodies, or redirects mid-run with Respond to Webhook.
- Use Parse JSON when an incoming field contains JSON as text.
- Run on a recurring schedule with the Scheduler Trigger.