- 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, 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
The Content-Type header is required for POST requests with a body. The default acceptable type is application/json. If you have configured a content-type allowlist (set by the platform team in the trigger definition), requests with any other content type return 415 Unsupported Media Type.
How the body becomes $trigger
TaskJuice normalizes every parsed body into an object before exposing it to your workflow. Three rules apply:
| 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 |
| Body that fails to parse as JSON | Wrapped under raw as a string | "not json" → $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.
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 fields (HMAC only)
When you select hmac-sha256 or hmac-sha512, three additional fields appear under the Replay Defense section. All three are optional, and you can set them independently:
| Field | Type | Default | Description |
|---|---|---|---|
Timestamp Header | string | unset | The header name where the upstream system sends the request timestamp (for example X-Timestamp for Stripe-style signing). When set, TaskJuice rejects requests whose timestamp falls outside the tolerance window. |
Tolerance (seconds) | integer, 1-3600 | unset (no enforcement) | The maximum age of a request, in seconds. A request older than this is rejected with 401. Only enforced when Timestamp Header is also set. |
Nonce Header | string | unset | The header name where the upstream system sends a unique nonce per request. When set, TaskJuice tracks recently-seen nonces and rejects duplicates within the tolerance window. |
A typical Stripe-style configuration uses Timestamp Header = Stripe-Signature-Timestamp, Tolerance = 300, and no nonce header.
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. |
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.
Successful response
When TaskJuice accepts and dispatches a webhook event, 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 200 with this body:
{
"received": true,
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}The same shape — there is no separate "empty" status leaked to the caller. Your run history shows whether a run was created.
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, the timestamp was expired, 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 | A previous request within the dedup window had the same dedup key. | Expected behaviour when deduplication is enabled. Wait until the dedup window expires or change the dedup key. |
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 Content-Type header was not in the trigger's content-type allowlist. | Send application/json or check the configured allowlist. |
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. | Capture the request_id from the response and contact support. |
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) |
| Replay tolerance window (HMAC) | 1 to 3,600 seconds (1 second to 1 hour) |
| Maximum PII redaction patterns | 50 per trigger |
| Maximum length per PII pattern | 200 characters |
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