---
title: "Incoming webhooks - Workflow Suite"
description: "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."
canonical: "https://docs.workflow-suite.app/incoming-webhooks"
---

# Incoming webhooks

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

1. Select the trigger, **Change trigger**, and pick **Incoming webhook**.
2. Save the workflow. Until then the panel says **Save the workflow to get its webhook URL**.
3. Copy the **Webhook URL** from the trigger panel. It is a secret URL on `shopify.workflow-suite.app`.
4. The panel also shows an **Example request** you can copy and run from a terminal.
5. 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.

```json
{ "sku": "ABC-1", "quantity": 12 }
```

A later step reads them as:

```liquid
{{ 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) |
| 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](https://docs.workflow-suite.app/developer-api-and-mcp.md).

> [!WARNING]
> **The URL is the key**
> Anyone who has the URL can start this workflow. Keep it out of tickets, chats and screenshots. If it leaks, replace it: the Developer API has a call that regenerates the URL, and the old one stops working at once. A workflow that accepts requests from outside should check what it got, for example with a Condition on a shared value in the body or on a header, before it changes anything.

## 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](https://docs.workflow-suite.app/trigger-filters.md).
- 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](https://docs.workflow-suite.app/recipe-set-stock-by-sku-webhook.md)
- [HTTP request](https://docs.workflow-suite.app/http-request.md) - the other direction: call another system from a workflow.
