Elizon Docs
Workflows

Triggers

The four ways a published workflow starts — API, webhook, form, and recurrence.

A published workflow can be started four ways from outside Studio. All four run the same deployed version of the graph and are subject to the same concurrency ceiling; they differ in who authenticates, and in how the payload reaches {{ trigger.* }}.

TriggerAuthenticated bytrigger.* bindingResponse
APIX-API-Keyyour input object, directly200 or 202
WebhookHMAC signature or an API keythe request body, under body202
Formits own mode — none, Basic, or Elizon loginthe submitted field values, under bodyan acknowledgment page (202)
Recurrencenothing — Elizon starts itempty ({})n/a

API

The path most integrations want. POST /api/v1/workflows/{workflowId}/run blocks for up to 30 seconds and returns the result; POST /api/v1/workflows/{workflowId}/run-async returns a run id immediately. Both are covered in full in the API reference.

The graph does not need a Manual trigger node for this to work — publishing is the only prerequisite.

Webhook

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 an HTTP Request node 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:

HeaderValue
X-Elizon-TimestampUnix seconds, as sent
X-Elizon-SignatureHMAC-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.com/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

StatusMeaning
202Accepted — body is { "executionId": "..." }. The run proceeds in the background.
400Authenticated, but the body is not valid JSON.
401Neither the signature nor the key authenticated the request.
404No active deployment matches that token — including an unpublished or deleted workflow.
413Body exceeded the 1 MB limit.
429Rate 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 Building a workflow.

Form

Lets a person start a run by filling in a hosted form — a request form, an intake questionnaire, an internal ticket — with nothing for you to build or host. Add a Form Trigger node, declare its fields, and publish.

https://<the origin you open Studio on>/forms/{formToken}

The form page is served by the app, not by the API, so this is not an api. host — the panel builds the link from the origin you are already on.

The formToken is opaque, is never the workflow's own id, and is minted by publishing — Studio's Form Trigger panel shows the real link once a deployment exists, and says the workflow is not published yet until then. It is carried forward unchanged every time you republish, so a URL you have already handed out keeps working across deploys. A workflow whose published graph has no Form Trigger node has no token at all.

Each field carries a name and a human-readable label — deliberately two things, since ticketId is the right data key and the wrong question to put in front of a person. Eleven field types are available (text, textarea, number, email, password, date, dropdown, radio, checkboxes, file, hidden); the three choice types carry their own options, and a file field carries its own Accepted file types and Allow multiple files options — see File uploads below.

The three auth modes

Set on the node, applied by the published deployment:

ModeWho gets in
NoneAnyone with the link.
BasicA username and password you set on the node. The password is bcrypt-hashed at publish time and never stored in the graph.
Elizon loginA signed-in, active member of the workflow's own organization — not any Elizon user.

Under Basic and Elizon login the field list is itself protected: an unauthenticated visitor cannot see what the form even asks, let alone submit to it. A visitor who is signed in but belongs to another organization gets a specific "not a member of this organization" state rather than a silent empty form or a second sign-in prompt.

Changing the mode or the username takes effect on the next publish — and a username change has to carry a new password with it, because a stored hash is only kept for the username it was set for; publishing a renamed Basic form without one is refused rather than left accepting the old pair. Changing nothing keeps the existing password working — an ordinary republish does not invalidate credentials you have already handed out.

File uploads

A file field does not carry bytes in the submission itself — it carries a reference, minted by its own upload endpoint:

POST /api/v1/forms/{formToken}/upload?field={fieldName}

One call per file, multipart/form-data with a single file part — mirroring how the field behaves on the hosted page: select a file and it uploads immediately, before the rest of the form is necessarily filled in. A successful upload returns:

{ "fileId": "…", "filename": "resume.pdf", "sizeBytes": 48213 }

Submit the returned fileId as the field's value — a plain string, or a string[] of them when the field's Allow multiple files option is on — never the file itself. The submission ingress resolves each id before the run starts, so a workflow reads the field's real value as:

{
  "filename": "resume.pdf",
  "contentType": "application/pdf",
  "sizeBytes": 48213,
  "text": "…extracted text…"
}

text is extracted once, at upload time: .txt/.csv/.md/.html/.xml are read directly, .pdf/.docx/.doc/.xlsx/.xls are parsed, and anything else (images included) falls back to a short description rather than failing the upload.

Two settings live on the field itself:

SettingEffect
Accepted file typesA comma-separated extension list (.pdf,.docx,.csv). Empty accepts any extension.
Allow multiple filesOff by default. On, the field submits an array and the upload endpoint is called once per file.
StatusMeaning
201Stored — body is the fileId/filename/sizeBytes shown above.
400field names no file field on this form.
422The file was rejected — wrong extension, over the size limit, or storage failed.
429Rate limit — the same one submissions share.

Upload size is a platform setting, not a per-form one

Every upload is capped at the same size regardless of the field's own configuration — 16 MB by default. A rejected file's 422 response names the real, current limit, so a form need not duplicate the number anywhere.

An upload nobody ever submits is deleted after 2 hours. Once a submission actually uses it, it is kept for 30 days from that submission — long enough to outlive a run parked on a Human Review node — after which it is deleted along with its stored bytes. Both windows, like the size limit, are platform settings rather than something a form author configures.

Submission responses

A submission is validated against the declared fields in the request, so an incomplete form reports its errors on the page rather than starting a run that fails where nobody is looking.

StatusMeaning
202Accepted — body is { "status": "received" }. The run proceeds in the background.
400A field failed validation — issues names each one — or the graph behind the form is empty.
401Credentials required, or wrong.
403Signed in, but not a member of the workflow's organization.
404No published form matches that token — including an unpublished or deleted workflow.
413Body exceeded the 1 MB limit.
429Rate limit, or the concurrency ceiling.

A submitter never sees the run's result

The acknowledgment is immediate and static: no execution id, no polling handle, nothing that waits for the run. A workflow can park 30 days on a Human Review node, which makes "wait for the result" the wrong contract for a browser tab. If the submitter needs an answer, have the graph send them one.

Submitted values arrive under body, keyed by each field's name, so a form-driven graph reads {{ trigger.body.email }} — the same envelope the webhook path uses.

Recurrence

Add a Recurrence node and Elizon starts the workflow on a schedule of its own — no caller, and nothing for you to host.

Two modes:

  • Cron — a cron expression plus an IANA time zone (for example Europe/Sofia). Use this whenever you need a specific wall-clock time.
  • Interval — "every N minutes/hours/days". Intervals are aligned to the Unix epoch, not to the moment you published, so "every 1 day" fires at 00:00 in the configured zone rather than at publish time.

The schedule is created, updated, or removed when you publish or unpublish — see Publishing. A misconfigured schedule (an empty cron string, an unrecognized time zone) fails the publish outright, with a message naming the problem, rather than leaving you with a "published" workflow that can never fire.

Scheduled runs carry an empty trigger payload, so a graph that depends on {{ trigger.* }} will not work on this path.

Where to go next