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:

nametyperequiredconstraintsdescription
enabledbooleanyesWhen true, the participant is redirected to the Endpoint URL. When false, the step sends a normal HTTP request.
targetobjectyes{ "type": "configured_url" }Where to send the participant. configured_url uses the step's request.url.

Requirements:

  • request.method must be GET
  • request.url must be an http:// or https:// 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

nametyperequireddescription
requestobjectyesThe HTTP request to send. See Request below.
waitForResponsebooleannoWhen true, the workflow waits for the HTTP response before continuing. Required if later steps should map response values. Default: false.
continueOnErrorbooleannoWhen true, the workflow continues when the request fails or returns an error status. Default: false. Has no effect when Redirect users is on.
listeningModeEnabledbooleannoWhen true, the next test request captures the response for field extraction. Streamline resets this to false after extraction completes. Default: false.
requestPayloadstringnoString 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.
responsePayloadstringnoString snapshot of a sample response body. Streamline sets this when a test succeeds; it feeds field extraction. Default: empty string.
mappableFieldsarraynoField descriptors for mapping into later steps. See Mappable fields. Default: empty array.
groupNodesarraynoDescribes repeating structures (arrays) in the response so their fields can be mapped as Repeating Records. Set by Streamline during field extraction. See Group nodes.
redirectobjectnoRedirects the participant instead of sending a request. Requires enabled and target. See Redirect.

Request

The request object defines the HTTP call.

nametyperequireddescription
methodstringyesHTTP method: GET, POST, PUT, or DELETE. Must be GET when Redirect users is on.
urlstringyesEndpoint URL. See Endpoint URL below.
authenticationobjectyesAuthentication settings. See Authentication below.
pathParamsarraynoPath parameter values. Each item has key and value (values support mapping). Keys must be unique.
queryParamsarraynoQuery string parameters. Each item has key and value (values support mapping). Keys must be unique.
headersarraynoCustom 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).
bodyobjectvariesRequest 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:// or https:// URL with no spaces. Path segments can use :name placeholders (for example https://api.example.com/users/:userId); supply their values in pathParams. Other schemes, such as ftp://, 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 an http:// or https:// 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.type set to NONE. Streamline rejects AUTHENTICATED with 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

nametyperequireddescription
typestringyesNONE (no authentication) or AUTHENTICATED (use a stored connection).
connectionIdstring or nullnoID 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.

nametyperequireddescription
requestModestringyesMust be RAW.
contentTypestringyesJSON or TEXT.
contentstring or objectnoBody 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.

nametyperequireddescription
requestModestringyesMust be MULTIPART.
contentarrayyesForm fields: each item has key and value (values support mapping).

RAW_BINARY

Send a single file as the entire request body.

nametyperequireddescription
requestModestringyesMust be RAW_BINARY.
rawBinaryFilevariesyesA 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).

nametyperequireddescription
idstringyes (variant A)File identifier. Use either id or downloadUrl, not both.
downloadUrlstringyes (variant B)File download URL. Use either id or downloadUrl, not both.
namestringyesFile name.
metadataobjectnoOptional: 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.

nametyperequiredconstraintsdescription
keystringyesFlattened JSON path in the response (e.g. body.status, body.user.email).
labelstringyesHuman-readable label for the field.
typestringyesData type (see list above).
exampleValuevariesnostring, number, boolean, or nullOptional example value stored with the field and shown in Streamline where sample values are displayed.
fieldCategorystringnoREGULAR, FLATTENED, or WILDCARDSet 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.


Did this page help you?