- Documentation
- Workflows
- Triggers
- Webhook
- Webhook trigger reference
Webhook trigger reference
Every field, header, status code, and error returned by the webhook trigger node, with concrete examples.
Overview
The webhook trigger fires a workflow whenever an external system sends an HTTP request to a unique URL that TaskJuice mints when you add the trigger to your workflow. This page documents every field you can configure, every response your callers will see — including the synchronous response modes that let a workflow author the reply — and every limit you need to design around.
If this is your first time building a webhook trigger, start with the quickstart instead — it walks through the minimum setup with a working curl example.
Webhook URL format
When you add a webhook trigger to a workflow, TaskJuice generates a unique URL of this shape:
https://api.taskjuice.ai/webhooks/<endpoint-token>The endpoint-token is a 36-character UUID v4. It is the only credential that proves a request can reach your workflow, so treat it like a password:
- Never paste it into public chat, screenshots, or version control
- Rotate it (regenerate the trigger) if it leaks
- Restrict where you store it just like you'd restrict an API key
The token is independent of your workflow ID and your subscription ID. Rotating it invalidates the previous URL and breaks any external system still calling the old one.
Open your workflow in the editor, click the webhook trigger node, and look for the Webhook URL field at the top of the configuration panel. Click the copy button to copy it to your clipboard.
Allowed HTTP methods
The webhook trigger accepts only GET and POST requests. Every other method returns 405 Method Not Allowed with an Allow header listing the supported methods.
| Method | Supported | Notes |
|---|---|---|
GET | Yes | Use when the upstream system carries data in query parameters or headers, not a body. HMAC authentication is not available for GET because there is no body to sign. |
POST | Yes | The default for almost every webhook integration. Required for HMAC authentication. |
PUT, PATCH, DELETE, HEAD, OPTIONS, TRACE | No | Returns 405 method not allowed. The Allow response header lists the supported methods. |
Request body
POST requests can carry a body. TaskJuice parses the body once and exposes it to your workflow as $trigger.
Content type
By default a webhook trigger accepts any content type — the body is decoded according to the incoming Content-Type header. JSON bodies (application/json) are parsed as JSON; form-encoded bodies (application/x-www-form-urlencoded) are decoded into a nested object (see Form-encoded bodies below); multipart and binary bodies extract their files (exposed as file references on $trigger); an unrecognized body that is not valid JSON falls back to raw.
If you set a content-type allowlist under the trigger's Advanced Settings → Request Validation (leave it empty to accept any type), a request whose Content-Type is not on the list returns 415 Unsupported Media Type.
How the body becomes $trigger
TaskJuice normalizes every parsed body into an object before exposing it to your workflow:
| Body shape received | What $trigger contains | Example |
|---|---|---|
| Plain JSON object | The object itself, unchanged | {"event":"test"} → $trigger.event is "test" |
| Top-level JSON array | Wrapped under items | [{"id":1},{"id":2}] → $trigger.items is the array |
| Top-level primitive (string, number, boolean) | Wrapped under value | 42 → $trigger.value is 42 |
| Form-encoded body | Decoded into a nested object | type=subscribe&contact[id]=1 → $trigger.contact.id is "1" |
| Non-form body that fails to parse as JSON | Wrapped under raw as a string | "not json" under text/plain → $trigger.raw is the literal string |
Empty body (POST with no payload) | The request returns 200 empty and no workflow run fires | — |
The wrapping is consistent so your downstream nodes can always assume $trigger is an object. If you need to iterate over a top-level array, point a Loop node at $trigger.items.
HTTP headers from the incoming request are used for authentication and (optionally) deduplication,
but they are not included in $trigger. If you need to read a value that the upstream system
only sends in a header, ask the upstream system to put it in the body instead.
Form-encoded bodies
When the incoming Content-Type is application/x-www-form-urlencoded, TaskJuice decodes the body into an object using the same bracket-notation convention as qs, Rack, and PHP — the format that providers like ActiveCampaign, Mailchimp, and Twilio send:
- Values are always strings.
qty=42becomes the string"42", not the number42. Cast downstream in a Smart Field or JSONata expression if you need a number or boolean. - Bracket keys nest.
contact[id]=1becomes$trigger.contact.id. - Repeated keys and
[]build arrays.tag=a&tag=bandtag[]=a&tag[]=bboth become$trigger.tag=["a","b"]. - Numeric indices become object keys, not array slots.
contact[fields][3]=xnests ascontact→fields→ a string key"3", never an array. In the mapping picker these segments are quoted for you; in hand-written JSONata, wrap a numeric segment in backticks — e.g.$trigger.contact.fields.`3`.
Authentication modes
You configure authentication on the trigger node from the Authentication Type dropdown. Five modes are supported:
| Mode | When to use it | Generated by TaskJuice |
|---|---|---|
none | Internal-only webhooks where the URL itself is the only credential and the network path is trusted. Acceptable for prototyping; not acceptable for production traffic from third parties. | Nothing |
bearer | Upstream system sends Authorization: Bearer <token>. Use for first-party webhooks from your own services or for providers that support bearer auth. | A bearer token, copyable from the trigger panel |
header | Upstream system sends a custom header (for example x-api-key: <secret>). Use when the provider documents a specific header name. | A header secret, copyable from the trigger panel |
hmac-sha256 | Upstream system signs the raw request body with a shared secret using HMAC-SHA256. Use for providers like GitHub, Stripe, and Slack. | A signing secret, copyable from the trigger panel |
hmac-sha512 | Same as HMAC-SHA256 but uses SHA-512. Use only when the provider requires SHA-512. | A signing secret, copyable from the trigger panel |
HMAC modes are not available when the trigger only allows GET, because there is no body to sign.
For step-by-step setup of each mode, see the authentication guide.
Replay defense (HMAC only)
The Webhook trigger has no replay-defense settings. HMAC modes verify the signature over the raw request body and nothing else — no timestamp tolerance, no nonce tracking — so a captured signed request passes verification if it is sent again. To keep a replayed or retried delivery from starting a second run, enable a deduplication strategy; Payload Hash collapses byte-identical replays within the window. App Webhook triggers verify each provider's own signing scheme where the provider offers one, and where the provider's signing recipe declares a timestamp tolerance TaskJuice enforces it automatically, with nothing to configure.
Deduplication fields
Deduplication prevents the same logical event from triggering more than one workflow run within a configured time window. You configure it under the Advanced Settings → Deduplication section. The default is Disabled.
| Strategy | When to use it | Required fields |
|---|---|---|
Disabled (none) | Every request triggers a run, even if the upstream system retries. Use only when your workflow is fully idempotent. | none |
Event ID (eventId) | The upstream system sends a stable event ID in the body. TaskJuice extracts the field at $.eventId (or $.id) and uses it as the dedup key. | none |
Payload Hash (payloadHash) | TaskJuice hashes the entire raw body and uses the hash as the dedup key. Catches byte-identical retries. | none |
Header Value (headerValue) | TaskJuice reads a specific header (for example X-Idempotency-Key) and uses its value as the dedup key. | Header Name |
Expression (expression) | You provide a JSONPath expression that resolves to a value in the body. The resolved value becomes the dedup key. | Key Expression |
| Field | Type | Default | Description |
|---|---|---|---|
Deduplication Strategy | enum | none | One of the five strategies above. |
Window (seconds) | integer, 1-86,400 | 3600 | The time window during which a duplicate dedup key suppresses additional workflow runs. Maximum is 24 hours. |
Header Name | string | — | Required when strategy is Header Value. The exact header name to read. |
Key Expression | string | — | Required when strategy is Expression. A JSONPath expression like $.data.id. |
Under a synchronous response mode, a retry that deduplication collapses is acknowledged with 200 and the canonical {"received":true,"request_id":"…"} body plus the header X-TaskJuice-Deduplicated: true. The original computed response is not replayed — the header tells an integrating caller that this delivery was collapsed onto an earlier one. Without a deduplication strategy, a caller's retry is a new request: it starts a second run and receives a second computed response.
App Webhook triggers — the per-app trigger nodes from the integrations catalog — have no deduplication settings because TaskJuice deduplicates provider retries for them automatically: a retried delivery is acknowledged, starts no second run, and appears under Monitoring → Activity as a Deduplicated row on the trigger's subscription. When the provider stamps each delivery with a unique id, retries are collapsed for up to three days, the longest retry schedule any supported provider documents. For a provider that sends no such id but documents a short retry schedule, such as MailerLite, TaskJuice recognises a retry by each event's exact content instead, for a shorter window sized to that provider's schedule; the app's page says how long. The match is per event, so an event identical to one already received within that window is treated as a repeat even when it arrives in a different delivery. Other providers that send no per-delivery id keep at-least-once behaviour.
Pre-execution filter fields
The pre-execution filter lets you accept the request but skip running the workflow when the body doesn't match a condition. Configure it under Advanced Settings → Pre-Execution Filter.
| Field | Type | Default | Description |
|---|---|---|---|
Pre-Execution Filter | toggle | off | Enables or disables filtering for this trigger. |
Mode | include | exclude | include | include runs the workflow only when the expression evaluates to true. exclude skips the workflow when the expression evaluates to true. |
Expression | string | — | A JSONPath boolean expression evaluated against the parsed body. Example: $.data.status == 'active'. |
A filtered request still returns 200, so the upstream system doesn't retry. The filter happens before the workflow run is created, so filtered events do not appear in your run history.
PII redaction fields
PII redaction replaces matching field values in the parsed body with a redaction marker before the workflow runs. Field names are matched against your regex patterns case-insensitively. Configure it under Advanced Settings → PII Redaction.
| Field | Type | Default | Description |
|---|---|---|---|
PII Redaction | toggle | off | Enables or disables redaction for this trigger. |
Field patterns | string[] | [] | A list of regular expressions, one per line. Field names matching any pattern are redacted. |
| Limit | Value |
|---|---|
| Maximum patterns | 50 |
| Maximum length per pattern | 200 characters |
| Pattern format | Any valid JavaScript regular expression |
Redaction applies before the body reaches $trigger, so your workflow expressions never see the original values.
Response modes
The Response mode setting on the trigger node (under Advanced Settings → Request Validation) decides what your caller receives and when. There are three modes, plus one the Phone Call trigger pins for you:
| Response mode | Value | What the caller receives | When it is sent |
|---|---|---|---|
| Immediate ACK | immediate_ack | 200 OK with {"received":true,"request_id":"…"}. The default. | As soon as the request passes validation, before any node runs |
| Respond node | respond_node | The status, headers, and body authored by a Respond node in the workflow. | When the Respond node runs, within the response timeout |
| Last node output | last_node | 200 OK with the terminal step's output as JSON. | When the last node finishes, within the response timeout |
| Stream (NDJSON) | streaming | 200 OK at once, then one JSON line per event as the workflow produces it — see Streaming. | Begins immediately; lines arrive until the stream ends |
| Phone call | phone_call | The answer document that connects the caller to the workflow's Voice Agent. | When the Voice Agent runs, within a 10-second budget |
The two buffered synchronous modes answer within the response timeout — 25 seconds by default; where your environment allows it, you can raise it per trigger.
The phone_call row is not a choice: the Twilio Phone Call trigger pins it, because a phone call has exactly one way to be answered. Its budget is shorter than the other two — telephony providers give an answer document about 15 seconds before they treat the call as failed, and TaskJuice answers at 10 with headroom to spare. A slow step between the trigger and the Voice Agent is therefore a real risk, and the publish bar warns you when it sees one.
Immediate ACK is the async model: TaskJuice acknowledges, then runs the workflow in the background. The two synchronous modes hold the HTTP connection open while the workflow runs, so the caller receives a computed response — the shape a chatbot platform, a voice gateway expecting TwiML, or a form backend needs.
Synchronous response modes apply to the generic Webhook trigger only. App Webhook triggers —
the per-app trigger nodes from the integrations catalog — must acknowledge the provider
immediately and always use Immediate ACK. If the two synchronous options are absent from the
Response mode dropdown, they are not yet enabled for your environment. An older value,
sync_validate, is retired: a trigger that still carries it behaves as Immediate ACK.
Immediate ACK
When TaskJuice accepts and dispatches a webhook event in Immediate ACK mode, it returns a 200 OK with this exact body:
{
"received": true,
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}The response also carries an X-Request-Id header with the same UUID:
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: 550e8400-e29b-41d4-a716-446655440000
{"received":true,"request_id":"550e8400-e29b-41d4-a716-446655440000"}The request_id is a per-request correlation token. It identifies a single delivery attempt and is the join key you use to find the corresponding entry in your run history or to reference when contacting support. The same UUID appears in the X-Request-Id header so tools that prefer headers (most observability platforms) pick it up automatically.
When the request is technically valid but produces no workflow run (an empty GET with no query parameters, for example), the response is the same 200 body. There is no separate "empty" status leaked to the caller; your run history shows whether a run was created.
Respond node
In Respond node mode the workflow answers the caller from a Respond system node placed anywhere downstream of the trigger. TaskJuice runs the nodes between the trigger and the Respond node, sends the response the moment the Respond node runs, and keeps running any nodes after it in the background.
The Respond node's configuration is the response:
| Field | Type | Default | Description |
|---|---|---|---|
| Status code | integer | 200 | 200–299 or 400–599. Informational (1xx) and redirect (3xx) codes are refused — a Respond node never redirects. |
| Content type | json | text | xml | empty | json | Selects the body source and the wire Content-Type: json → application/json; charset=utf-8, text → text/plain; charset=utf-8, xml → text/xml; charset=utf-8, empty → no body and no Content-Type. |
| Body | mapped input or template | — | json: the node's mapped input, authored on the Output Mapping surface like any other node. text and xml: a body template with {{ }} markers resolved from $trigger, $steps, and the other expression aliases. empty: nothing. |
| Headers | name → value map | {} | Up to 16 headers. Allowed: Cache-Control, Content-Language, Content-Disposition, Vary, and any custom X- header except X-Request-Id and X-TaskJuice-*. Everything else is refused at publish and at runtime (see Forbidden headers below). |
Rules that apply to every Respond node:
- Redaction. A
jsonbody is passed through the same sensitive-key redaction as run data before it is serialised: a value under one of the platform's known credential keys —token,access_token,refresh_token,api_key,secret,client_secret,password,credentials,authorization— reaches the caller as[REDACTED]. Echoing$triggerback as JSON strips those keys; any other key, and anytextorxmltemplate, is sent exactly as authored. - Size. The serialised body must fit in 256 KiB. A larger body fails the Respond node with
RESPOND_BODY_TOO_LARGE, the caller receives500 internal error, and the run viewer shows the size — a truncated document is never sent. X-Request-Idis always the platform's. It is set last, from the request's own id, so no authored header can override it.- Forbidden headers.
Set-Cookie,Location,Content-Type(derived from the content type),Content-Length,Transfer-Encoding,Connection,Keep-Alive,Upgrade,TE,Trailer,Server, and anyProxy-*orAccess-Control-*header. Header names must be valid HTTP tokens of at most 64 characters; values must be visible ASCII, at most 1 KiB, with no line breaks. The node panel reports the exact refusal inline, and publish refuses the same names with the same message. - First Respond wins. The request is answered by the first Respond node the run reaches; the rest of the run continues in the background, so a second Respond node sends nothing, still succeeds, and records
no-transportin its output. The sameno-transportoutcome is recorded by a Respond node that runs in a node test, in Run now, or in a workflow whose trigger is on Immediate ACK. - No response point reached. If the run completes without ever running a Respond node, the caller receives the Immediate ACK body,
200 {"received":true,"request_id":"…"}.
A worked example for a voice gateway: content type xml, status 200, body template <Response><Say>{{$steps.reply.data.text}}</Say></Response> answers 200, Content-Type: text/xml; charset=utf-8, X-Request-Id: <request id>, and the rendered TwiML document.
Which nodes may run before a Respond node
Because the caller is waiting, every node on a path from the trigger to a Respond node must be able to finish inside the request. Publishing refuses — naming the node — any node that pauses the run on such a path: Delay, wait-for-event nodes, Form nodes, and approval or human-input steps.
What else may run before the Respond node depends on your environment. Where webhooks are served with extended synchronous responses (the Response timeout field is shown on the trigger), these also run before the answer: Call Workflow (await and for-each), Parallel and its join, Loop in parallel mode, AI agents in durable mode, and heavy (file-handling) steps. Elsewhere, publishing refuses them on a path to the Respond node too, with the message "… pauses or fans out the workflow …".
A node of one of these kinds elsewhere in the graph — on a branch that does not lead to the Respond node — is fine: if the run takes that branch, TaskJuice hands the run to the background and the caller receives 202 (see below). A Respond node placed after another Respond node, or in a workflow whose trigger is on Immediate ACK, publishes with a warning: it will only ever run as a no-op.
Last node output
In Last node output mode there is no Respond node. TaskJuice runs the whole workflow and answers 200 OK with the output of the terminal step as JSON:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Request-Id: 550e8400-e29b-41d4-a716-446655440000
{"greeting":"Hello Ada","token":"[REDACTED]"}- What "terminal step output" is. The body is the
datamember of the step that ends the run — exactly what$steps.<that step>.dataresolves to for a downstream node. The step'smetastays in the run viewer and is not sent. If the step's output has nodatamember, the whole output is sent. - Which step is terminal. A step with no outgoing connection ends the run. A step whose outgoing conditions select no path also ends the run, and answers with its own output. The response comes from the step that ends the run — the last step to complete. When a Branch or Switch leads to several leaves, that is the leaf on the path the run actually took, and the arms not taken contribute nothing. Where webhooks are served with extended synchronous responses, fan-out may also run before the answer (see Which nodes may run before a Respond node); the caller is then answered when the whole run completes, with the output of whichever leaf finished last, and the other leaves contribute nothing.
- Fixed envelope. Status
200,Content-Type: application/json; charset=utf-8,X-Request-Id, no authorable headers. The body goes through the same redaction and 256 KiB bound as ajsonRespond node. A terminal output over the bound answers500 internal errorwith the run reading completed — the step is never failed after the fact. - No Respond node allowed. The two modes are mutually exclusive: a workflow on Last node output that contains a Respond node is refused at publish, naming the node. Remove it, or switch the trigger to Respond node mode.
- Whole-graph eligibility. Every node reachable from the trigger — not only the nodes on one path — must be free of the kinds that may not run before a Respond node (see Which nodes may run before a Respond node — on an environment with extended responses, only the pausing kinds), because any of them could run inside the request.
Streaming (NDJSON)
In Stream (NDJSON) mode the caller receives 200 OK with Content-Type: application/x-ndjson; charset=utf-8 and Cache-Control: no-store immediately, and then one JSON object per line as the workflow produces output. The mode is offered only where your environment streams webhook responses; if the option is absent from the Response mode dropdown, it is not available there.
Every line has a type:
type | Fields | Meaning |
|---|---|---|
begin | runId | Always the first line, exactly once. |
item | nodeId, data | Output as it is produced: each Respond node's body, and an AI agent's text as the model writes it (data: {iteration, text}). |
heartbeat | — | Sent every 30 seconds while nothing else flows, so idle connections stay open. Ignore it. |
end | runId, status | The stream finished. Nothing follows it. |
error | reason: deadline, handed_off, run_failed | The stream ended early. deadline: the response timeout passed. handed_off: the run moved to the background. run_failed: a step failed before the stream ended. In every case nothing follows it, and for deadline and handed_off the run keeps going in the background. |
{"type":"begin","runId":"7a6c…"}
{"type":"item","nodeId":"agent-1","data":{"iteration":0,"text":"Looking that up"}}
{"type":"item","nodeId":"respond-1","data":{"ok":true}}
{"type":"end","runId":"7a6c…","status":"completed"}Rules for streaming runs:
- Respond nodes write lines, not responses. Each Respond node the run reaches writes its body as one
itemline; their status codes and headers are ignored (the200left atbegin). Turn on a Respond node's End stream toggle to end the stream right after its line — the rest of the workflow keeps running in the background. Without an End stream node, the stream ends withendwhen the run completes. - One line is at most 256 KiB. A larger body is replaced by
{"truncated":true}under itsnodeId; the run itself is unaffected. - Nothing may pause the run before an End stream node. Publishing refuses a Delay, wait, Form or approval step on a path to a Respond node whose End stream is on, and — when no node has End stream on — anywhere the run can reach.
- If your client disconnects, the run continues in the background.
- The stream lasts at most 870 seconds, the longest response timeout streaming allows.
Response timeout
Where your environment allows synchronous answers longer than 25 seconds, the trigger shows a Response timeout (seconds) field for Respond node, Last node output and Stream (NDJSON). It is stored as responseTimeoutMs:
| Mode | Default | Range | At the timeout |
|---|---|---|---|
| Respond node, Last node output | 25 s | 1–280 s | 504 {"error":"gateway timeout","request_id":"…"}; the run continues |
| Stream (NDJSON) | 25 s | 1–870 s | {"type":"error","reason":"deadline"} ends the stream; the run continues |
A value above the mode's maximum is clamped when you switch to a mode with a lower maximum. In an environment without extended responses the field is hidden and the budget is 25 seconds whatever is stored.
Timing, capacity, and hand-offs
Both synchronous modes share the same envelope of guarantees:
| Situation | Status | Body | What happens to the run |
|---|---|---|---|
| The response point (Respond node or terminal step) is reached within the budget | authored / 200 | authored / the terminal step's data | Continues in the background after a Respond node; completes |
| The response timeout (25 seconds by default) elapses before the response point. The budget is measured from the moment TaskJuice received the request; a node already running is never interrupted. | 504 | {"error":"gateway timeout","request_id":"…"} | Continues in the background and completes; a Respond node reached later records no-transport |
| A node parks the run (an approval gate, a retry with back-off, a connection request), a heavy-lane or fan-out node is reached, an operator pauses or cancels the run, the request is too large to hand to the in-line executor, the platform could not run the request in-line (a throttled or faulted executor), or the trigger's response mode was changed after the workflow was published — from Immediate ACK to a synchronous mode, or between Respond node and Last node output | 202 | {"received":true,"request_id":"…"} | Handed to the background: accepted, but no synchronous answer could be authored. 202 says exactly that; 200 would claim completion. The run continues in the background. A mode change takes effect only on republish: republish a workflow whose trigger mode changed after it was published, and until then every request is answered 202. |
| A node fails before the response point | 500 | {"error":"internal error","request_id":"…"} | The run fails and the error trigger fires, as in Immediate ACK |
| The run completes without reaching a Respond node (Respond node mode only) | 200 | {"received":true,"request_id":"…"} | Completed |
| 5 synchronous requests are already in flight for the workspace (the sixth is refused) | 503 + Retry-After: 2 | {"error":"service unavailable","request_id":"…"} | Nothing ran; the request is safe to retry after two seconds |
The request_id on every one of these responses is the same join key described under Using the request_id, so a 504 or 202 can be matched to its run in the run history.
Error responses
Every error response shares the same shape:
{
"error": "<short generic message>",
"request_id": "<uuid>"
}The HTTP status code carries the actual meaning. The body's error field is a short, lowercase, generic phrase chosen from a fixed list. The same string is returned for every reason a given status class can fire — for example, every 401 says "authentication failed" regardless of whether the signature was wrong or the header was missing. This is intentional: a more specific message would tell an attacker which guess was closer to correct.
| Status | error value | What it means | What to check |
|---|---|---|---|
400 | bad request | The request was malformed at the protocol level (missing route parameters, invalid URL). | Verify your URL path matches the format above. |
401 | authentication failed | The signature, token, or header value didn't match what the trigger expects. | Re-verify your secret and the auth mode you configured. The exact reason is hidden by design. |
404 | not found | The endpoint token doesn't correspond to any trigger. | Confirm the URL token from your trigger configuration. The trigger may have been rotated or the workflow archived. |
405 | method not allowed | You sent a method other than GET or POST (or the trigger only allows one of them). | Check the Allow header on the response — it lists the supported methods. |
409 | duplicate request | Reserved. A delivery that deduplication collapses is acknowledged with 200 and no run (plus X-TaskJuice-Deduplicated: true under a synchronous mode), so this status is not returned today. | Nothing — a duplicate is a 200; check the run history to confirm only one run was created. |
413 | payload too large | The request body exceeded the configured maximum size. | Reduce the payload size or contact the trigger owner to raise the limit. |
415 | unsupported media type | The trigger has a content-type allowlist configured and the request's Content-Type was not on it. | Send a Content-Type on the allowlist, or clear the allowlist under Advanced Settings → Request Validation to accept any type. |
422 | validation failed | The pre-execution filter ran and rejected the event. | Check the filter expression and the body — the request was authentic but didn't match the filter. |
500 | internal error | Something went wrong on TaskJuice's side — or, under a synchronous response mode, a node failed before the response point or the authored body exceeded 256 KiB. | Capture the request_id from the response and check the run in your run history before contacting support. |
503 | service unavailable | Synchronous modes only: the workspace already has 5 synchronous requests in flight. Nothing ran. | Retry after the Retry-After header's value (2 seconds). |
504 | gateway timeout | Synchronous modes only: the response timeout elapsed before the response point. The run continues. | Shorten the path to the Respond node or terminal step, or switch the trigger to Immediate ACK and poll the run. |
For 5xx responses, the body never includes any details about the underlying failure — no exception class names, no stack traces, no service names. The request_id is the only identifier you should report when filing a support ticket.
Using the request_id
The request_id returned in every response is the join key that connects three places:
- The HTTP response the upstream system received (body field and
X-Request-Idheader) - The run history entry in your TaskJuice dashboard for that delivery attempt
- The internal logs TaskJuice support uses to investigate failures
When you contact support about a failed delivery, include the request_id exactly as it appeared in the response. Without it, support has to search by approximate timestamp, which is slower and less reliable.
The request_id is generated fresh per request. It does not identify your workflow, your subscription, or your tenant — only the specific HTTP attempt. Sharing one is safe.
Accessing trigger data in workflow expressions
Inside your workflow, every node downstream of the trigger can read the request body via the $trigger expression alias. The shape of $trigger follows the body normalization rules from the Request body section.
| Body the upstream sent | Expression to read it |
|---|---|
{"event":"checkout.completed","amount":4999} | $trigger.event → "checkout.completed" |
{"event":"checkout.completed","amount":4999} | $trigger.amount → 4999 |
[{"id":"a"},{"id":"b"},{"id":"c"}] | $trigger.items → the array; iterate with a Loop node pointed at $trigger.items |
42 (a primitive) | $trigger.value → 42 |
"not json" (parse failure) | $trigger.raw → the literal string |
$trigger does not contain headers. If you need a value the upstream system only sends in a header, ask the upstream system to put it in the body.
Limits
Hard limits enforced by the platform. These are not configurable from the workflow editor.
| Limit | Value |
|---|---|
| Endpoint token length | 36 characters (UUID v4) |
| Default request body size limit | 1,048,576 bytes (1 MB) |
| Allowed HTTP methods | GET, POST only |
| Deduplication window | 1 to 86,400 seconds (1 second to 24 hours) |
| Maximum PII redaction patterns | 50 per trigger |
| Maximum length per PII pattern | 200 characters |
| Synchronous response budget | The response timeout (25 seconds by default; up to 280 s, or 870 s streaming, where extended responses are available) from receipt of the request |
| Synchronous requests in flight | 5 per workspace |
| Authored response body | 256 KiB (serialised UTF-8) |
| Authored response headers | 16 per Respond node; names ≤ 64 characters, values ≤ 1 KiB |
The body size limit is enforced before authentication, so an oversized payload is rejected with 413 without consuming any auth verification work.
Related
- Send your first webhook — set one up in five minutes with curl
- Authenticate your webhook — configure HMAC, bearer, or custom-header auth
- Filter, deduplicate, and redact webhook events — recipes for the advanced settings
- How webhook triggers work — the conceptual model