Incoming Webhook Step
Trigger a new workflow execution when an external system sends an HTTP request to a generated endpoint.
The Incoming Webhook step is a trigger — it must be the first step in a workflow and it starts a new execution each time an external system sends an HTTP request to the generated endpoint. Use it to integrate with third-party systems that push data rather than respond to API calls.
For mapping placeholder syntax, see Mapping.
When you create a project whose workflow starts with an Incoming Webhook step, the project response includes a webhookSessionStartUrl in the __meta object. That is the URL your external system calls to start a session. The URL uses the step's stable publicId, so it does not change when you update the workflow.
Authentication
The step's authenticationType controls how Streamline validates incoming requests.
| authenticationType | behaviour |
|---|---|
NONE | The endpoint is public. Any caller can trigger a session without credentials. |
PAT | Callers must include a valid bearer token in the Authorization header. Streamline validates the token against the organization's authentication provider. |
HMAC | Callers must sign the raw request body with a shared secret and include the signature in the Streamline-Signature header. Requires an integrationId referencing a configured integration that holds the HMAC secret. |
When authenticationType is HMAC, you must also provide integrationId (the integration that stores the shared secret). For NONE and PAT, integrationId is optional.
HMAC signature
For HMAC-authenticated webhooks, every request must include a Streamline-Signature header containing the HMAC-SHA256 digest of the raw request body:
Streamline-Signature: sha256={hex}| Detail | Value |
|---|---|
| Algorithm | HMAC-SHA256 |
| Header | Streamline-Signature: sha256={hex} |
| Input | Exact raw request body bytes (UTF-8) |
| Secret | The shared secret from the integration referenced by integrationId |
Streamline recomputes the digest from the raw bytes it receives and compares it to the value in the header. If the digests do not match, the request is rejected with 401. If the header is missing or malformed (e.g. missing the sha256= prefix), the request is rejected with 400.
HMAC-authenticated webhooks are commonly used with embedded workflows, where your server signs the payload and starts the session before passing the resumeUrl to a client-side iframe. For a complete walkthrough — including code examples in Node.js, Python, Java, and cURL — see .
Configuration
| name | type | required | description |
|---|---|---|---|
authenticationType | string | yes | How Streamline authenticates incoming requests. One of NONE, PAT, or HMAC. |
integrationId | string | conditional | Integration holding the authentication secret. Required when authenticationType is HMAC; optional otherwise. |
method | string | yes | HTTP method to accept: GET or POST. |
listeningModeEnabled | boolean | no | Set to true to put the step in listening mode so it captures a sample request and extracts fields. Streamline resets this to false after extraction completes. Default: false. |
requestPayload | string | no | JSON string snapshot of the captured test request (body, query parameters, and headers). Streamline writes this when a test request succeeds. Default: empty string. |
mappableFields | array | no | Fields extracted from the captured payload, available for mapping in later steps. See Mappable fields. Default: empty array. |
groupNodes | array | no | Entity group descriptors for repeating collections within the payload. See Group nodes. |
Mappable fields
Each entry in mappableFields describes a field from the incoming request that later steps can map. Field keys are prefixed with body., query., or headers. depending on where they appear in the request.
| name | type | required | description |
|---|---|---|---|
key | string | yes | The field path in the captured request (e.g. body.orderId, query.status, headers.x-request-id). |
label | string | yes | Human-readable label for the field. |
type | string | yes | Data type: string, number, boolean, date, datetime, time, object, or array. |
exampleValue | string, number, boolean, or null | no | Sample value stored with the field and shown where Streamline displays sample data. |
fieldCategory | string | no | Set by Streamline during extraction. REGULAR — a top-level scalar value (e.g. body.status). FLATTENED — a value from a nested object, flattened to dot-notation (e.g. body.address.city). WILDCARD — a value inside a repeating array, referenced with a wildcard path (e.g. body.items.*.name). |
Populating mappable fields
mappableFields is a regular configuration property — you can populate it automatically from a test request, or write the array directly when you create or update the project.
Option A: Extract from a test request
This is the fastest path when you have a representative payload. The flow is:
- Enable listening mode — update the step configuration in the project and set
listeningModeEnabledtotrue. - Send a test request — append
?test=trueto the step'swebhookSessionStartUrl(from the project response's__metaobject) and send a POST or GET:
POST {webhookSessionStartUrl}?test=true
The request body, query parameters, and headers you send become the sample payload. Authentication follows the step's authenticationType — for NONE, no credentials are needed; for PAT, include a bearer token; for HMAC, include the Streamline-Signature: sha256={hex} header (see HMAC signature).
Streamline validates that the step is in listening mode, stores the payload as requestPayload on the step configuration (sensitive headers such as Authorization and Cookie are stripped before storage), and starts field extraction in the background. The endpoint returns immediately with:
{
"success": true,
"message": "Test webhook received successfully. Field extraction is being processed in background."
}If the step is not in listening mode, the request is rejected with a 400 error. If an extraction is already in progress, the request is rejected until the current extraction completes.
- Retrieve the extracted fields — once extraction finishes, Streamline writes the results to
mappableFieldson the step configuration and resetslisteningModeEnabledtofalse. Fetch the project viaGET /v1/projects/{projectId}to read the populatedmappableFieldsandrequestPayloadfrom the step's config.
The extracted fields can then be referenced in later steps using the step's description as the mapping source (e.g. {{Incoming webhook.body.orderId}}).
If the results are not what you expected, enable listening mode again, send a different payload, and Streamline re-extracts.
Option B: Set mappable fields directly
If you already know the payload structure, you can skip listening mode entirely and include mappableFields in the step configuration when you create or update the project:
PATCH /v1/projects/{projectId}
{
"workflow": {
"steps": [
{
"type": "webhook",
"description": "Incoming webhook",
"config": {
"authenticationType": "NONE",
"method": "POST",
"mappableFields": [
{ "key": "body.orderId", "type": "string", "label": "Order ID" },
{ "key": "body.amount", "type": "number", "label": "Amount" },
{ "key": "body.status", "type": "string", "label": "Status" }
]
},
"edges": []
}
]
}
}Each key must match the path that will appear in the incoming request at runtime (prefixed with body., query., or headers.).
Customizing after extraction
Whether fields were extracted automatically or written directly, you can modify them at any time by updating the project. Add fields, remove fields, rename labels, or change types — then include the updated mappableFields array in the step's config. Later steps that reference these fields by key use whatever is currently stored on the step.
Group nodes
When the captured payload contains arrays, Streamline creates groupNodes entries that describe the repeating structure. Each group node identifies an array path so that later steps can iterate over collection items.
| name | type | required | description |
|---|---|---|---|
id | string | yes | Unique identifier for the group node. |
label | string | yes | Human-readable name for the collection. |
path | string | yes | Dot-notation path to the array in the payload (e.g. body.orders). |
wildcardPath | string | yes | Path with * replacing array indices (e.g. body.orders.*). |
fields | array | no | Scalar fields within each item of the collection. Each entry has path (string) and type (string). Default: empty array. |
itemCount | number | no | Number of items found in the sample payload for this collection. |
isRepeating | boolean | no | Whether this collection repeats (always true for detected arrays). Default: false. |
parentPath | string or null | no | Path of the parent group node for nested arrays. null for top-level collections. |
displayRoot | boolean | no | Whether this node is the top-level entry point when the payload contains nested arrays. |
Examples
Let's start with an unconfigured webhook trigger — no fields extracted yet.
{
"authenticationType": "NONE",
"method": "POST",
"listeningModeEnabled": false,
"requestPayload": "",
"mappableFields": []
}Here's what the step configuration looks like after sending a sample payload and extracting fields. Notice that listeningModeEnabled is back to false and the fields use body. prefixed keys.
{
"authenticationType": "NONE",
"method": "POST",
"listeningModeEnabled": false,
"requestPayload": "{\"body\":{\"orderId\":\"ord-789\",\"customerEmail\":\"[email protected]\",\"amount\":149.99,\"status\":\"placed\"},\"query\":{},\"headers\":{}}",
"mappableFields": [
{ "key": "body.orderId", "type": "string", "label": "Order ID", "exampleValue": "ord-789" },
{ "key": "body.customerEmail", "type": "string", "label": "Customer Email", "exampleValue": "[email protected]" },
{ "key": "body.amount", "type": "number", "label": "Amount", "exampleValue": 149.99 },
{ "key": "body.status", "type": "string", "label": "Status", "exampleValue": "placed" }
]
}Here is a PAT-authenticated webhook that accepts GET requests.
{
"authenticationType": "PAT",
"method": "GET",
"listeningModeEnabled": false,
"requestPayload": "",
"mappableFields": []
}Updated 29 days ago
