Webhook trigger
Let another system start a run by POSTing to a URL unique to your deployment.
The Webhook trigger (trigger_http, shown in Studio as the HTTP Request trigger) gives your published workflow a URL that another system can POST to. Its field list is the request body the workflow expects, checked before the run starts.
Configuration
| Field | Type | Default | Description |
|---|---|---|---|
method | GET | POST | PUT | PATCH | DELETE | POST | The HTTP method the trigger documents. |
fields | list of { name, type, required } | [] | The request-body fields. type is string, number, boolean or object. Studio shows the matching OpenAPI request body beside them. |
webhookSecret | string | '' | The signing secret for HMAC-signed calls. Studio shows it next to the URL; treat it as a credential. |
Declared fields are checked when the run starts: a request missing a required field, or sending the wrong type, fails the run before any node runs, naming each field. The check applies only when this trigger is the one that fired — a scheduled run of the same workflow is not rejected for a missing body.
Lets a third-party system that has no Elizon API key — a payment provider, a CRM, a form backend — start a run by POSTing to a URL unique to your deployment.
POST /api/v1/workflows/{workflowId}/trigger/{webhookToken}The webhookToken is opaque and is never the workflow's own id. It is carried forward unchanged every time you republish — only a workflow that has never been published mints a new one — so a URL you have already handed to a third-party system keeps working across deploys (see Publishing). Studio shows you both the URL and its signing secret. Add a Webhook trigger node (trigger_http) to the graph so the payload has somewhere to land.
Authenticating a webhook call
Either mechanism is sufficient on its own:
HMAC signature. Send two headers:
| Header | Value |
|---|---|
X-Elizon-Timestamp | Unix seconds, as sent |
X-Elizon-Signature | HMAC-SHA256(secret, "<timestamp>.<raw body>"), hex |
The timestamp is signed inside the payload, not merely sent alongside it, so a captured request cannot be replayed with a fresh timestamp header. Requests whose timestamp is more than 5 minutes from the server's clock are rejected. Sign the exact bytes of the header and the raw body — re-serializing either will produce a signature that does not match.
import hashlib, hmac, json, time, httpx
secret = "whsec_..." # from Studio
body = json.dumps({"orderId": "A-1001"})
ts = str(int(time.time()))
sig = hmac.new(secret.encode(), f"{ts}.{body}".encode(), hashlib.sha256).hexdigest()
httpx.post(
f"https://api.elizon.ai/api/v1/workflows/{workflow_id}/trigger/{token}",
headers={
"Content-Type": "application/json",
"X-Elizon-Timestamp": ts,
"X-Elizon-Signature": sig,
},
content=body,
)API key. Send X-API-Key: elz_... (or the equivalent Authorization: Bearer elz_...) and no signature is required. The key must belong to the workflow's own organization, and a project-scoped key to the workflow's project.
X-Elizon-Timestamp is required either way
Replay protection is applied to both authentication paths, so X-Elizon-Timestamp is
mandatory even when you authenticate with an API key and send no signature at all. A request
without it — or with one outside the window above — gets exactly the same uninformative 401 as a
bad signature.
Webhook responses
| Status | Meaning |
|---|---|
202 | Accepted — body is { "executionId": "..." }. The run proceeds in the background. |
400 | Authenticated, but the body is not valid JSON; or the body holds more than 20,000 values (array elements plus object keys across the whole body, each array or object counting 5 more) or nests deeper than 256 levels, which is refused before authentication. |
401 | Neither the signature nor the key authenticated the request. |
404 | No active deployment matches that token — including an unpublished or deleted workflow. |
413 | Body exceeded the 1 MB limit. |
429 | Rate limit, or the concurrency ceiling. |
404 is deliberately uninformative
An unknown token, a token belonging to another organization, and a token whose workflow has been
unpublished all return the identical 404 body. That is intentional — a webhook URL is a
credential, and distinguishing those cases would let a caller probe for valid ones.
Because the body arrives under body, a webhook-driven graph reads its payload as {{ trigger.body.orderId }} — see Output below.
Output
The node's output is the run's input. A webhook call nests the request body under body:
| Reference | Value |
|---|---|
{{ trigger.body.<field> }} | A field of the request body, for example {{ trigger.body.orderId }}. |
{{ run.trigger.type }} | webhook for a webhook call; api when the same workflow is run through the public API. |
{{ run.trigger.id }} | This node's id. |
A run started through POST /workflows/{id}/run binds your input object flat, so the same field is {{ trigger.orderId }} there. The declared fields are checked either way.
Limits and errors
- One per workflow. The URL is issued once per workflow, so a second Webhook trigger is refused at save.
- Bodies over 1 MB are refused with
413. - Bodies over 20,000 values (array elements plus object keys across the whole body, each array or object counting 5 more) or nested deeper than 256 levels are refused with
400, even under 1 MB. - See Webhook responses for every status code.
Example
name: New order
description: Handles an order posted by the shop.
graph:
nodes:
- id: order-received
type: trigger_http
position: { x: 0, y: 0 }
data:
method: POST
fields:
- { name: orderId, type: string, required: true }
- { name: total, type: number, required: true }
- id: check-order
type: ai_agent
position: { x: 280, y: 0 }
data:
mode: existing
agent: agents.order-checker
agentName: Order checker
inputMappings: []
edges:
- { id: order-to-check, source: order-received, target: check-order }The agent reads {{ trigger.body.orderId }} and {{ trigger.body.total }}.
In a Package
Declare a webhook trigger in the manifest, with the payload it documents:
triggers:
- id: new-order
type: webhook
payload:
fields:
- { name: orderId, type: string, required: true }
- { name: total, type: number, required: true }The webhook URL and its token are generated when the installer activates the Package. On the node itself:
- Travels:
method,fields - Converted: none
- Never carried:
webhookSecret
See Package triggers.