Webhook Node
此内容尚不支持你的语言。
The webhook node calls an external HTTP endpoint directly, using a URL, method, headers, and body you configure inline on the node itself. Unlike a tool node or an action node, there’s no prior registration step — you point it at a URL and it calls it. Use it for one-off integrations you don’t want to formalize as a registered MCP connector or Custom Action.
See Workflows Overview for how this node type fits into the broader node/edge model.
Config fields
Section titled “Config fields”WebhookNodeConfig:
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The endpoint to call. Validated against an SSRF guard at workflow-save time — requests to private/internal IP ranges are rejected before the workflow can even be published. |
method | 'POST' | 'GET' | 'PUT' | Yes | The HTTP method to use. |
headers | Record<string, string> | No | Static headers to send with the request (for example, a static API key header). Values here are not templated — for a value that needs to reference an earlier node’s output, put it in bodyTemplate instead. |
bodyTemplate | string | No | A Handlebars-templated JSON string, rendered with the same escaping used for tool and action argument templates — quotes, backslashes, and control characters in interpolated values are handled for you, so you never need to hand-roll JSON escaping. See Best Practices for the templating rules and the }}} parsing gotcha. |
outputVar | string | No | The context variable the response body is stored in, for use by later nodes. If omitted, the response is discarded — useful for pure fire-and-forget notifications. |
async | boolean | No | See Blocking vs. async mode below. |
timeoutMs | number | No | Request timeout in milliseconds. Defaults to 10000 (10 seconds). Clamped to a hard ceiling of 30000 (30 seconds) regardless of what you configure — a timeoutMs above that is silently capped, not rejected. |
fetchAsBase64 | { urlTemplate, bodyField }[] | No | Fetch one or more files and embed each as base64 into bodyTemplate’s rendered JSON body. Added 2026-09-22 — see Fetching files as base64 (including from S3) below. |
Blocking vs. async mode
Section titled “Blocking vs. async mode”By default (async unset or false), the webhook node blocks — the workflow waits for the HTTP call to complete (or time out) before continuing to the next node. This is what you want when a later node needs the response (via outputVar), or when you need to know the call succeeded before proceeding.
Setting async: true switches to fire-and-forget mode: the workflow fires the request and immediately continues to the next node without waiting for a response. Use this for side-effect calls where the outcome doesn’t affect the conversation — logging an event to an external analytics endpoint, notifying a Slack channel, pinging an internal system of record. In this mode, outputVar has nothing meaningful to store (the workflow has already moved on before a response could arrive), so don’t set one.
Worked example
Section titled “Worked example”A workflow that answers a support question and, in the background, logs the interaction to an external analytics endpoint without slowing down the reply:
{ "id": "log_interaction", "type": "webhook", "label": "Log Interaction (fire-and-forget)", "config": { "url": "https://analytics.example.com/events", "method": "POST", "headers": { "content-type": "application/json" }, "bodyTemplate": "{\"event\": \"support_question_answered\", \"question\": \"{{userMessage}}\"}", "async": true, "timeoutMs": 5000 }, "position": { "x": 350, "y": 150 }}And a blocking example where the reply depends on the webhook’s response — checking a generic order-status endpoint that isn’t behind an MCP server:
{ "id": "check_order", "type": "webhook", "label": "Check Order Status", "config": { "url": "https://orders.example.com/api/status", "method": "POST", "headers": { "content-type": "application/json" }, "bodyTemplate": "{\"orderId\": \"{{orderId}}\"}", "outputVar": "order_status_result", "timeoutMs": 8000 }, "position": { "x": 250, "y": 200 }}{ "id": "respond_order", "type": "response", "label": "Respond with Order Status", "config": { "messageTemplate": "Your order status: {{order_status_result}}", "mood": "helpful" }, "position": { "x": 350, "y": 200 }}Fetching files as base64 (including from S3)
Section titled “Fetching files as base64 (including from S3)”Added 2026-09-22. fetchAsBase64 fetches one or more files server-side, base64-encodes each, and injects the result into bodyTemplate’s rendered JSON body at a dot path — for calling an endpoint that expects an embedded file (a file_base64 field, or a nested applicant.resume), without you having to build that encoding yourself.
Each entry is { urlTemplate, bodyField }:
urlTemplate— a Handlebars template rendered against context, yielding either anhttp(s)://URL or ans3://bucket/keyURI. A bare{{attachmentUrl}}is the common case; anything richer ({{uploads.0.url}}, a templated prefix) also works.bodyField— the dot path in the renderedbodyTemplateJSON at which to place the base64 string. Intermediate objects are created as needed; an existing non-object value at an intermediate segment fails the node rather than silently overwriting it.
bodyTemplate is required whenever fetchAsBase64 is set — there’s no body to inject a fetched file into otherwise, and this is enforced at save time, not just at execution.
{ "id": "submit_application", "type": "webhook", "label": "Submit Application with Résumé", "config": { "url": "https://partner.example.com/api/applications", "method": "POST", "headers": { "content-type": "application/json" }, "bodyTemplate": "{\"candidateName\": \"{{candidateName}}\", \"file_base64\": \"\"}", "fetchAsBase64": [ { "urlTemplate": "{{attachmentUrl}}", "bodyField": "file_base64" } ], "outputVar": "submission_result", "timeoutMs": 15000 }, "position": { "x": 300, "y": 220 }}Up to 4 entries per node. Each fetched file is capped at 10 MB (the same limit that applies to the action node’s equivalent field) — a larger file fails the node rather than truncating silently. The base64 string never touches ctx (unlike action’s fetchAsBase64Var, which writes it to a context variable) — it’s injected directly into the rendered body and nowhere else, which keeps a multi-megabyte string out of the run’s persisted context and out of Redis.
s3://bucket/key URIs are supported alongside ordinary HTTPS URLs — useful when the file you want to attach already lives in your own S3 bucket rather than being reachable over the public internet. This is fetched with the platform’s own AWS credentials, not anything tenant-specific, so access is gated explicitly rather than left to whatever those credentials can reach: the platform’s own configured bucket is always denied, and any other bucket must be explicitly granted to your tenant (set operationally today, not via a GraphQL mutation — ask if you need one granted). An ungranted bucket fails the node outright rather than silently returning nothing. A presigned S3/CloudFront HTTPS URL doesn’t need any of this — it’s just an ordinary HTTPS fetch.
Gotchas
Section titled “Gotchas”timeoutMsis clamped to 30 seconds no matter what you set. If your endpoint can genuinely take longer than that, a webhook node isn’t the right fit — consider a design where the external system calls back into your own integration instead.- The URL is checked for SSRF risk at save time, not just at execution time. A workflow update that sets
urlto a private/internal address is rejected before it’s even stored — this is the same guard applied to action node URLs. As of 2026-09-08, a hostname that doesn’t resolve at all is rejected too (previously it was silently allowed through). - Redirects are not followed (as of 2026-09-08).
webhooknode calls now reject any 3xx response from the target outright, instead of transparently following it — no legitimatewebhooktarget needs to redirect, and this closes an SSRF gap where a redirect to a private address bypassed the URL check entirely. If your target legitimately redirects (e.g. anhttp://→https://upgrade, a trailing-slash normalization), pointurldirectly at the final address. bodyTemplateis JSON-templated, not text-templated. It uses the same escaping asargsTemplateon tool and action nodes — see Best Practices for what that means for values with embedded quotes or newlines, and the triple-brace (}}}) parsing quirk to watch for when a{{...}}expression is the last thing before the closing}.headersvalues are static, not templated. If you need a header value derived from conversation context, there’s no field for that today — onlybodyTemplateis templated.- In
async: truemode, don’t setoutputVar. The workflow has already moved to the next node before any response could arrive, so there’s nothing meaningful to store. - To see exactly what
bodyTemplateactually rendered to, checkrenderedRequeston this node’snodeTraceentry — captured on both success and failure. See Observability & Debugging Your Agent for the fullnodeTraceshape. fetchAsBase64requiresbodyTemplateto also be set. There’s nowhere to inject the fetched file’s base64 string otherwise — this is rejected at save time, not discovered at run time.- The
s3://and HTTP(S) allowlists are opposite defaults, on purpose. An HTTP(S)url/urlTemplateis fetchable from any public host unless your tenant has explicitly configured a restricting allowlist. Ans3://URI is the reverse — no bucket is fetchable until it’s explicitly granted, since it’s reached with the platform’s own credentials rather than yours. Don’t assume “our allowlist is empty” means S3 access is open; it means the opposite.
Next steps
Section titled “Next steps”- Workflows Overview — the full node/edge model.
- Workflow Best Practices — templating rules that apply to
bodyTemplate. - Tool Node and Action Node — the registered alternatives to an inline webhook call.
- LLM Node, Condition Node, Router Node, Response Node, Sub-Agent Node — other node types.
- Worked Examples & Demos — complete multi-node workflows.
- Workflows API Reference — the mutations for creating and publishing workflows.