apikey / Authorization: Bearer {API_KEY} headers needed). Authentication is per-webhook: the secret token returned by the arm step must be presented either in an Authorization: Bearer <secret> header or as a ?token=<secret> query parameter.
Common Patterns
A workflow’s webhook lives in one of two states:- Deployed: the webhook is permanently active and accepts unlimited calls.
- Armed for single use: the webhook accepts exactly one call within the arm window; after firing, you must re-arm. This suits one-shot integrations (testing, manual triggers) where you don’t want a long-lived endpoint.
POST /api/webhooks/
Trigger a workflow. The request body becomes the workflow’s input variables. Returns the execution outcome synchronously (the workflow runs inline with a 5-minute timeout).The webhook ID returned when the workflow was armed/deployed. Must be a valid UUID.
Bearer <webhook_secret> — preferred over the query-param form.Webhook secret. Use this when you can’t set headers (e.g., a webhook source that only supports a URL).
Use one auth mechanism or the other (Bearer header OR
?token= query). The server checks the header with auth_header.lower().startswith("bearer ") (note the trailing space). If your Authorization header is exactly Bearer (trailing space, no token), which is what Bearer ${secret || ""} produces when secret is falsy, the server reads an empty token and returns 401 without consulting ?token=. Bearer with no trailing space falls through to the query param.The trigger endpoint executes the workflow inline and returns the final output. For long-running workflows that exceed the 5-minute synchronous limit, design the workflow to acknowledge the request quickly and continue work asynchronously (e.g. dispatch via a
general_api block to a background processor).Security model
What the trigger endpoint does and doesn’t do. Webhook security is where unstated assumptions get exploited, so the guarantees are spelled out below. What it does:- Verifies the secret via constant-time comparison.
hmac.compare_digeston the server side prevents timing-attack style secret guessing. - Validates the secret BEFORE the deploy-or-arm state gate. An invalid secret cannot consume the single-use arm slot, so bad requests don’t accidentally disarm a workflow before legitimate ones can fire.
- Atomic single-use disarm. For armed (single-use) workflows, the platform issues a
RETURNING idUPDATE that disarms in the same statement. Only the first of N concurrent requests with a valid secret wins; the rest seewebhook_armed_until = NULLand return403.
- No HMAC of the request body. The webhook secret authenticates the caller, not the request. A man-in-the-middle who can read the URL or
Authorizationheader can replay the request with any body. For sensitive triggers, use TLS termination at your network boundary and a trusted client. Don’t rely on the webhook itself to prove the body was authored by the upstream system. - No timestamp / nonce / replay-window protection. The secret doesn’t expire and isn’t tied to a specific request. An attacker who captures one valid request can replay it until you rotate the secret.
- No retry / redelivery. A failed trigger is gone; the trigger endpoint is fire-and-forget from the upstream’s perspective. If you need at-least-once delivery semantics, configure your upstream system’s own retry policy.
- No request idempotency. Two identical calls (same body, same time) trigger two executions. Pair with workflow logic that’s idempotent if you need that.
code block validating the signature, branching to response on failure). The platform won’t do it for you.
Error Responses
Errors return{"error": "<message>", "error_code": "<UPPER_SNAKE_CODE>"}. The 401 responses additionally omit error_code (they’re a plain {"error": "Unauthorized"}).
| Status | error_code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | webhook_id is not a valid UUID |
| 401 | — | Missing or incorrect webhook secret |
| 403 | — | Webhook is not active (workflow not deployed and no live arm token) |
| 404 | WORKFLOW_NOT_FOUND | No workflow has a webhook block with this ID |
| 500 | EXECUTION_FAILED | The workflow ran but raised an error. The execution_id is included for debugging |
| 504 | EXECUTION_TIMEOUT | The workflow exceeded the 5-minute synchronous limit |