Incoming webhooks
Let an ERP, a form or any other system start a workflow with a POST request to a secret URL, and read its body, query and headers in your steps.
The trigger Incoming webhook gives a workflow its own secret URL. Another system, such as an ERP, a warehouse, a form tool or a script, sends a POST request to that URL, and the workflow starts with the request as its event.
Set it up
- Select the trigger, Change trigger, and pick Incoming webhook.
- Save the workflow. Until then the panel says Save the workflow to get its webhook URL.
- Copy the Webhook URL from the trigger panel. It is a secret URL on
shopify.workflow-suite.app. - The panel also shows an Example request you can copy and run from a terminal.
- Turn the workflow on. A workflow that is off answers with an error and starts nothing.
A workflow created from a template, duplicated or created over the API gets its own URL. A duplicate never shares the URL of the original.

What the steps read
| Variable | Holds |
|---|---|
event.payload.body |
The JSON body of the request |
event.payload.query |
The query parameters of the URL |
event.payload.headers |
A small set of request headers: content type, user agent, request id, x-event-type, x-event-id, x-signature and x-hub-signature-256 |
Example: the other system sends a SKU and a quantity.
{ "sku": "ABC-1", "quantity": 12 }
A later step reads them as:
{{ event.payload.body.sku }} and {{ event.payload.body.quantity }}
To test without the other system, paste a body like the one above into Event (JSON) in the Test panel, under body.
What the caller gets back
| Answer | Meaning |
|---|---|
202 with started: true |
A run was started |
202 with started: false |
Nothing started. reason is filtered (the trigger filter rejected it) or suppressed (loop protection or the plan limit stopped it) |
| 401 | The workflow asks for a signature, and the request has none or a wrong one. See the signature check below |
| 404 | The URL is not known |
| 405 | The request was not a POST |
| 409 | The workflow is turned off |
| 413 | The body is larger than 256 KB |
| 429 | More than 120 requests in a minute. Wait a minute and try again |
The answer comes at once. It does not wait for the run and does not carry its result. The other system can look the run up over the Developer API. See Developer API and MCP.
Signature check (optional)
On top of the secret URL, a workflow can ask every request for a signature. The trigger panel has two optional fields:
- Signing secret - the name of a secret from the Secrets page, for example
WEBHOOK_SIGNING_SECRET. Enter the name, never the value. See Secrets. - Signature header - the request header that holds the signature. Empty means
x-signature. GitHub sendsx-hub-signature-256, Shopifyx-shopify-hmac-sha256.
When a signing secret is set, a request starts the workflow only if that header holds the HMAC-SHA256 of the raw request body under that secret, written as hex, as sha256=<hex> or as base64. Any other request is answered with 401 and starts nothing. So is every request when the named secret does not exist: the check never falls open.
The signature covers the body exactly as it was sent, byte for byte. Sign the same bytes you send:
curl -X POST "$URL" -H "content-type: application/json" -H "x-signature: $(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')" -d "$BODY"
Without a signing secret the trigger works as before: the secret URL is the only protection.
Good to know
- Use a trigger filter to ignore requests you do not want. Rejected requests start nothing and do not count against your plan. See Trigger filters.
- End the workflow with a Fail the run step on the path where the data is wrong, for example when no variant has the SKU. The run then shows as Failed with your message, and you get the failure alert.
- The webhook URL is never returned to a read-only API key.
Related
- Recipe: let another system set stock by SKU
- HTTP request - the other direction: call another system from a workflow.
