Outbound Webhook Step
Send HTTP requests to external endpoints during a workflow, optionally capture the response for later steps, or redirect the participant to an external URL.
The Outbound Webhook step sends an HTTP request to an external URL while a workflow runs. Use it to notify another system, create or update a record in a third-party API, or upload a file. You can run the request in the background, or wait for a response and pass response data to steps that follow.
You can also turn on Redirect users to send the participant to an external URL in their browser (for example a payment page, portal, or another workflow). When Redirect users is on, the workflow ends after this step.
For mapping placeholder syntax, see Mapping. For Repeating Records in JSON request bodies, see Repeating Records in JSON bodies.
Waiting for a response
Set waitForResponse to true if the workflow should pause until the HTTP call finishes. When the external service returns a body (and a content type is present), Streamline stores that response so later steps can map values from it. response.statusCode is always available as a numeric field when the response is captured.
For JSON responses, the body is flattened into dot-notation paths that start with body. (for example, body.id or body.user.email). Non-JSON bodies, or JSON that fails to parse, are exposed as a single string field named body.
If waitForResponse is false, the workflow does not capture the HTTP response for mapping into later steps.
Continue on error
When continueOnError is true, HTTP error status codes (4xx/5xx) or network failures do not stop the workflow. The step can finish with a completed-with-errors state instead of failing the run. Default is false (failures stop the step).
continueOnError has no effect when Redirect users is on, because Streamline does not send an HTTP request in that case.
Timeouts
When Streamline sends the outbound request, it waits up to 5 seconds to connect to the destination, then up to 60 seconds for a response.
If the destination does not accept the connection or return a response in time, Streamline treats the call as a network failure and retries it (up to 3 attempts, including the first try). If those attempts still fail, the step fails unless you set continueOnError to true.
These timeouts apply to the HTTP call Streamline makes. They do not apply when Redirect users is on and the participant's browser opens the Endpoint URL.
Redirect
Redirect users sends the participant to the Endpoint URL in their browser instead of sending an HTTP request from Streamline. After this step, the workflow is complete — later steps do not run.
To turn it on, set redirect in the step configuration with enabled set to true and a target:
| name | type | required | constraints | description |
|---|---|---|---|---|
enabled | boolean | yes | When true, the participant is redirected to the Endpoint URL. When false, the step sends a normal HTTP request. | |
target | object | yes | { "type": "configured_url" } | Where to send the participant. configured_url uses the step's request.url. |
Requirements:
request.methodmust beGETrequest.urlmust be anhttp://orhttps://URL (static, or mapped from an earlier step)- Path parameters can fill path segments only; they cannot supply the host
To send someone to another workflow: first use a normal outbound webhook (waitForResponse: true) to get a resume URL, then use a second outbound webhook with Redirect users on and that URL mapped into the Endpoint URL.
Configuration
| name | type | required | description |
|---|---|---|---|
request | object | yes | The HTTP request to send. See Request below. |
waitForResponse | boolean | no | When true, the workflow waits for the HTTP response before continuing. Required if later steps should map response values. Default: false. |
continueOnError | boolean | no | When true, the workflow continues when the request fails or returns an error status. Default: false. Has no effect when Redirect users is on. |
listeningModeEnabled | boolean | no | When true, the next test request captures the response for field extraction. Streamline resets this to false after extraction completes. Default: false. |
requestPayload | string | no | String snapshot of the outbound request. Streamline sets this when a test succeeds; you can also set it when updating the project. Field extraction reads this together with responsePayload. |
responsePayload | string | no | String snapshot of a sample response body. Streamline sets this when a test succeeds; it feeds field extraction. Default: empty string. |
mappableFields | array | no | Field descriptors for mapping into later steps. See Mappable fields. Default: empty array. |
groupNodes | array | no | Describes repeating structures (arrays) in the response so their fields can be mapped as Repeating Records. Set by Streamline during field extraction. See Group nodes. |
redirect | object | no | Redirects the participant instead of sending a request. Requires enabled and target. See Redirect. |
Request
The request object defines the HTTP call.
| name | type | required | description |
|---|---|---|---|
method | string | yes | HTTP method: GET, POST, PUT, or DELETE. Must be GET when Redirect users is on. |
url | string | yes | Endpoint URL. See Endpoint URL below. |
authentication | object | yes | Authentication settings. See Authentication below. |
pathParams | array | no | Path parameter values. Each item has key and value (values support mapping). Keys must be unique. |
queryParams | array | no | Query string parameters. Each item has key and value (values support mapping). Keys must be unique. |
headers | array | no | Custom HTTP headers. Each item has key and value (values support mapping). Header names must be unique, ignoring case (X-Request-Id and x-request-id count as the same header). |
body | object | varies | Request body. See Body modes. Required for POST and PUT. Optional for DELETE. Not used for GET. |
Endpoint URL
url accepts one of two forms:
- A URL template: an absolute
http://orhttps://URL with no spaces. Path segments can use:nameplaceholders (for examplehttps://api.example.com/users/:userId); supply their values inpathParams. Other schemes, such asftp://, are rejected when you save the project. - A single mapping placeholder that supplies the whole URL, for example
{{`Notify next workflow`.`body.resumeUrl`}}. The placeholder must be the entire value, and it must resolve to anhttp://orhttps://URL string when the step runs. Path parameter detection does not apply to a mapped URL.
A mapped URL can only be used with
authentication.typeset toNONE. Streamline rejectsAUTHENTICATEDwith a mapped URL so stored connection credentials are never sent to a host that comes from runtime data. To call an authenticated API, use a URL template and map only the path parameters, query parameters, headers, or body.
Authentication
| name | type | required | description |
|---|---|---|---|
type | string | yes | NONE (no authentication) or AUTHENTICATED (use a stored connection). |
connectionId | string or null | no | ID of the stored connection. Set it when type is AUTHENTICATED. Use null or omit it when type is NONE. |
Body modes
The shape of body is determined by requestMode.
RAW
Send JSON or plain text. String values support mapping placeholders.
| name | type | required | description |
|---|---|---|---|
requestMode | string | yes | Must be RAW. |
contentType | string | yes | JSON or TEXT. |
content | string or object | no | Body content. For JSON, either a string or an object whose string values support mapping, including syntax for Repeating Records. |
When contentType is JSON and content is a string, the string must be valid JSON. Mapping placeholders inside it must be wrapped in quotes, for example "{\"name\": \"{{`Form`.`userName`}}\"}". Unquoted placeholders are rejected because they could produce invalid JSON at runtime. The only exception is a content value that is a single placeholder on its own, such as "{{`Form`.`payload`}}".
MULTIPART
Send multipart/form-data. Each part is either text (string value) or a file resolved from a file reference or mapping placeholder.
| name | type | required | description |
|---|---|---|---|
requestMode | string | yes | Must be MULTIPART. |
content | array | yes | Form fields: each item has key and value (values support mapping). |
RAW_BINARY
Send a single file as the entire request body.
| name | type | required | description |
|---|---|---|---|
requestMode | string | yes | Must be RAW_BINARY. |
rawBinaryFile | varies | yes | A file reference or a mapping placeholder (e.g. {{Form.uploadedDocument}}). See File object. |
File object
Use when referencing a file by ID or download URL (instead of only a mapping placeholder).
| name | type | required | description |
|---|---|---|---|
id | string | yes (variant A) | File identifier. Use either id or downloadUrl, not both. |
downloadUrl | string | yes (variant B) | File download URL. Use either id or downloadUrl, not both. |
name | string | yes | File name. |
metadata | object | no | Optional: contentType, size, createdAt, updatedAt. |
Mappable fields
mappableFields declares which response properties later steps can map and supplies type hints. Each key must match the flattened JSON path your endpoint returns. You can write the key with or without the body. prefix (body.user.email and user.email both match). Later steps always see response fields with the body. prefix.
Supported type values include: string, number, boolean, date, datetime, object, array, and file.
| name | type | required | constraints | description |
|---|---|---|---|---|
key | string | yes | Flattened JSON path in the response (e.g. body.status, body.user.email). | |
label | string | yes | Human-readable label for the field. | |
type | string | yes | Data type (see list above). | |
exampleValue | varies | no | string, number, boolean, or null | Optional example value stored with the field and shown in Streamline where sample values are displayed. |
fieldCategory | string | no | REGULAR, FLATTENED, or WILDCARD | Set by Streamline during extraction. REGULAR — a top-level value (e.g. body.status). FLATTENED — a value from a nested object (e.g. body.user.email). WILDCARD — a value inside a repeating array (e.g. body.orders.*.id). |
No other properties are allowed on a mappable field.
Group nodes
When the response contains arrays, Streamline records each repeating structure in groupNodes during field extraction so later steps can use it as Repeating Records. groupNodes uses the same shape as on the Incoming Webhook step. See Group nodes for the property table. You don't need to set it yourself; existing configurations without groupNodes remain valid.
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 update the project.
Option A: Extract from a test request
To derive fields from a real HTTP call, use the outbound webhook test endpoint. Append ?test=true to the step's webhookSessionStartUrl from the project response:
POST {webhookSessionStartUrl}?test=true
Use the same authentication as for other /v1/sessions requests (bearer token). The body is a JSON array of objects with id, type, and sampleValue for each mapped input the test request should resolve; send [] when there are no mapped inputs. A successful response includes responsePayload, requestPayload, and extractedFields. On your next project update, persist extractedFields as mappableFields, and persist responsePayload and requestPayload so field extraction has both snapshots.
Option B: Set mappable fields directly
If you already know the response structure, you can include mappableFields in the step configuration when you create or update the project via PATCH /v1/projects/{projectId}:
PATCH /v1/projects/{projectId}
{
"workflow": {
"steps": [
{
"type": "outbound_webhook",
"description": "Notify CRM",
"config": {
"request": { "..." : "..." },
"waitForResponse": true,
"mappableFields": [
{ "key": "body.id", "label": "User ID", "type": "string" },
{ "key": "body.status", "label": "Status", "type": "string" }
]
},
"edges": []
}
]
}
}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 update the step's mappableFields array in the project. Later steps that reference these fields by key use whatever is currently stored on the step.
Examples
Simple GET with no authentication or body:
{
"request": {
"method": "GET",
"url": "https://api.example.com/users",
"authentication": { "type": "NONE", "connectionId": null }
}
}Redirect to a fixed URL (Redirect users on):
{
"request": {
"method": "GET",
"url": "https://example.com/thank-you",
"authentication": { "type": "NONE", "connectionId": null }
},
"redirect": {
"enabled": true,
"target": { "type": "configured_url" }
}
}Redirect to a URL from an earlier step (Redirect users on):
{
"request": {
"method": "GET",
"url": "{{`Notify next workflow`.`body.resumeUrl`}}",
"authentication": { "type": "NONE", "connectionId": null }
},
"redirect": {
"enabled": true,
"target": { "type": "configured_url" }
}
}POST with a raw JSON body (string form):
{
"request": {
"method": "POST",
"url": "https://api.example.com/webhook",
"authentication": { "type": "NONE", "connectionId": null },
"body": {
"requestMode": "RAW",
"contentType": "JSON",
"content": "{\"message\": \"Hello\"}"
}
}
}POST with connection auth, path/query parameters, JSON object body with mappings, and response mapping:
{
"request": {
"method": "POST",
"url": "https://api.example.com/users/:userId",
"authentication": {
"type": "AUTHENTICATED",
"connectionId": "my-api-connection"
},
"pathParams": [{ "key": "userId", "value": "{{`Form`.`userId`}}" }],
"queryParams": [{ "key": "format", "value": "json" }],
"headers": [{ "key": "X-Custom-Header", "value": "custom-value" }],
"body": {
"requestMode": "RAW",
"contentType": "JSON",
"content": {
"name": "{{`Form`.`userName`}}",
"email": "{{`Form`.`userEmail`}}",
"metadata": {
"source": "workflow",
"requestId": "{{`Form`.`requestId`}}"
}
}
}
},
"waitForResponse": true,
"mappableFields": [
{
"key": "body.id",
"label": "User ID",
"type": "string",
"exampleValue": "usr-12345"
},
{
"key": "body.status",
"label": "Status",
"type": "string",
"exampleValue": "created"
}
]
}Multipart POST with form fields:
{
"request": {
"method": "POST",
"url": "https://api.example.com/submit",
"authentication": { "type": "NONE", "connectionId": null },
"body": {
"requestMode": "MULTIPART",
"content": [
{ "key": "name", "value": "{{`Form`.`userName`}}" },
{ "key": "description", "value": "Static description" }
]
}
}
}Binary file upload with a file object:
{
"request": {
"method": "POST",
"url": "https://api.example.com/upload",
"authentication": { "type": "AUTHENTICATED", "connectionId": "file-api" },
"body": {
"requestMode": "RAW_BINARY",
"rawBinaryFile": {
"id": "file-123",
"name": "document.pdf",
"metadata": {
"contentType": "application/pdf",
"size": 1024000
}
}
}
}
}Binary file upload with a mapping placeholder:
{
"request": {
"method": "PUT",
"url": "https://api.example.com/files",
"authentication": { "type": "NONE", "connectionId": null },
"body": {
"requestMode": "RAW_BINARY",
"rawBinaryFile": "{{`Form`.`uploadedDocument`}}"
}
}
}DELETE with an optional body:
{
"request": {
"method": "DELETE",
"url": "https://api.example.com/resources/:resourceId",
"authentication": {
"type": "AUTHENTICATED",
"connectionId": "api-connection"
},
"pathParams": [{ "key": "resourceId", "value": "{{`Form`.`resourceId`}}" }],
"body": {
"requestMode": "RAW",
"contentType": "JSON",
"content": "{\"reason\": \"cleanup\"}"
}
}
}Repeating Records in JSON bodies
When requestMode is RAW and contentType is JSON, the outbound body can include Repeating Records from earlier steps.
The examples below assume an earlier step named Incoming webhook exposes Repeating Records with field labels such as Order ID, Customer Name, Line items, Product, Quantity, Variant SKU, and Variant Name.
Pass Repeating Records through unchanged
Use a single placeholder when the destination should receive the same array shape that came from the earlier step.
{
"request": {
"method": "POST",
"url": "https://api.example.com/orders",
"authentication": { "type": "NONE", "connectionId": null },
"body": {
"requestMode": "RAW",
"contentType": "JSON",
"content": {
"orderId": "{{`Incoming webhook`.`Order ID`}}",
"customerName": "{{`Incoming webhook`.`Customer Name`}}",
"lineItems": "{{`Incoming webhook`.`Line items`}}"
}
}
}
}lineItems resolves to an array, not a JSON string. For example, if the source step has two Line items rows, the outbound body resolves to:
{
"orderId": "ord-1001",
"customerName": "Acme Corp",
"lineItems": [
{
"Product": "Widget A",
"Quantity": 2,
"Variant SKU": "WA-RED",
"Variant Name": "Widget A - Red"
},
{
"Product": "Widget B",
"Quantity": 1,
"Variant SKU": "WB-BLUE",
"Variant Name": "Widget B - Blue"
}
]
}Output a new array shape
Use a one-item array template when the destination needs a different shape. Streamline expands that template once for each record in Repeating Records.
Keep all mapped field labels in the same array template tied to the same Repeating Records data set. Do not mix fields from different Repeating Records data sets in one template.
{
"request": {
"method": "POST",
"url": "https://api.example.com/orders/export",
"authentication": { "type": "NONE", "connectionId": null },
"body": {
"requestMode": "RAW",
"contentType": "JSON",
"content": {
"items": [
{
"name": "{{`Incoming webhook`.`Product`}}",
"count": "{{`Incoming webhook`.`Quantity`}}",
"channel": "ERP_EXPORT"
}
]
}
}
}
}For example, if the source step has two Line items rows, the outbound body resolves to:
{
"items": [
{
"name": "Widget A",
"count": 2,
"channel": "ERP_EXPORT"
},
{
"name": "Widget B",
"count": 1,
"channel": "ERP_EXPORT"
}
]
}Output nested Repeating Records
Nested arrays work the same way. Add a nested one-item array template inside the parent template, and keep each level's placeholders aligned to that level's Repeating Records label.
The same rule applies at each level: one template, one Repeating Records data set. For example, a parent row should not combine fields from Line items with fields from a different top-level array, and a nested variantRows template should only use fields from the matching nested array.
{
"request": {
"method": "POST",
"url": "https://api.example.com/orders/export",
"authentication": { "type": "NONE", "connectionId": null },
"body": {
"requestMode": "RAW",
"contentType": "JSON",
"content": {
"orderId": "{{`Incoming webhook`.`Order ID`}}",
"exportedRows": [
{
"product": "{{`Incoming webhook`.`Product`}}",
"quantity": "{{`Incoming webhook`.`Quantity`}}",
"variantRows": [
{
"sku": "{{`Incoming webhook`.`Variant SKU`}}",
"variantName": "{{`Incoming webhook`.`Variant Name`}}"
}
]
}
]
}
}
}
}For example, if the source step has two Line items rows (the first with two variants and the second with one), the outbound body resolves to:
{
"orderId": "ord-1001",
"exportedRows": [
{
"product": "Widget A",
"quantity": 2,
"variantRows": [
{ "sku": "WA-RED", "variantName": "Widget A - Red" },
{ "sku": "WA-BLUE", "variantName": "Widget A - Blue" }
]
},
{
"product": "Widget B",
"quantity": 1,
"variantRows": [{ "sku": "WB-GREEN", "variantName": "Widget B - Green" }]
}
]
}Use the Repeating Records label, such as Line items, when you want to pass an entire array through. Use field labels such as Product, Quantity, Variant SKU, and Variant Name when you want Streamline to build custom objects inside an array template.
Updated 1 day ago
