Start a workflow with an HTTP call#
An HTTP trigger gives a workflow an address: a request to it starts an execution, with what the request carries as the execution's event. This page adds the trigger, makes the address, and calls it each of the three ways a call can prove it may; then it covers the workflow as an API, repeats, limits and the address's last calls.
Before you begin#
- A workflow (see Build and run a workflow), and the editor
role: making an address needs
flows:trigger, writing the triggerflows:update. - The workflow's inputs all have defaults, or are not required: a call gives the event only.
curl(andopenssl, for a signed call).
1. Add the trigger#
- On the workflow's page press Edit (new version).
- From the palette's Start group, drag HTTP call onto the graph (or click it).
- In its panel: Answer (At once or With the outputs), How a call proves it may, Calls a minute, No repeats: key and Also answer GET. Its Recipe menu fills them: Webhook, Workflow as an API, Signed webhook (GitHub, Stripe, banks), Events with an id (no repeats).
- Keep Runs (and its triggers) use this version from now on ticked and press Write the version.
spec:
triggers:
- id: bank
kind: http
auth: hmac # url (default), bearer, hmac
signature_header: X-Signature-256
dedupe: "${{ event.id }}"
dedupe_seconds: 600
astra iris workflows apply -f <file> writes the version. Every field
is in the specification.
Only the current version's triggers answer: a version written without
becoming current changes nothing until it is made current. A change to a
trigger (its auth, its mode) applies to calls within 10 seconds.
2. Make its address#
- On the workflow's page, open Triggers.
- Under the trigger, press Make the address.
- Copy what is shown — it is shown only this once: the URL,
and for
bearerorhmacthe Bearer token or Signing secret. Try it is acurlcommand that calls it.
The same panel is under the trigger's box in the editor, once the workflow is saved.
Addresses are made through the console's API, with an API token, for a workspace and one of its clusters:
$ curl -sS -X POST "https://console.astralyx.cloud/api/v1/orgs/<org>/workspaces/<workspace>/clusters/<cluster>/flow-hooks" \
-H "Authorization: Bearer $ASTRALYX_TOKEN" -H "Content-Type: application/json" \
-d '{"flow": "card-spending", "trigger": "bank"}'
It answers 201 with {id, flow, trigger, url, secret, auth, how,
mode}: how says which header to send. A trigger that is not an HTTP
trigger of the workflow's current version answers 404
FLOW_TRIGGER_NOT_FOUND.
The address is https://console.astralyx.cloud/api/v1/hooks/<id> — with
/<secret> after it when the trigger's auth is url. The secret starts
with ih_. Calls run as you, the person who made the address (see
Who it runs as).
Keep the secret like a password
Anyone who has the secret starts the workflow as you. Astralyx keeps a digest of it, and the secret itself sealed (to check signatures with); it is never shown again. If it leaks, make a new secret.
3. Call it#
Below, <address> and <secret> stand for what you copied.
Sign the exact bytes you send, with the secret, and send the hex digest
in the trigger's header (X-Signature-256 unless it names another),
as sha256=<hex> or <hex>:
SECRET='<secret>'
BODY='{"id": "t1", "text": "COMPRA APROVADA R$ 250,00 PADARIA SAO JOAO"}'
SIG=$(printf %s "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -sS -X POST '<address>' -H 'Content-Type: application/json' \
-H "X-Signature-256: sha256=$SIG" -d "$BODY"
A sender that signs its own webhooks this way (GitHub's
X-Hub-Signature-256, for one) is given the address and the secret,
and the trigger names its header in signature_header.
Only the way the trigger names is accepted: with auth: bearer, a secret
in the address is not enough.
What a call carries#
The call's body becomes the execution's event:
| The call | event |
Read as |
|---|---|---|
Content-Type: application/json |
The JSON. A body that is not JSON is refused (400 HOOK_BODY). |
${{ event.text }}, event.amount > 100 |
application/x-www-form-urlencoded |
An object of its fields (text) | ${{ event.name }} |
| Any other type | {"text": "<the body>"}, or the JSON when the text is a JSON object or list |
${{ event.text }} |
GET (with allow_get: true), or an empty body |
An object of the query's fields | ${{ event.camera }} for ?camera=gate |
A body is at most 128 KiB (400 HOOK_BODY_TOO_LARGE): send a file's
place, not the file. Without allow_get, a GET is refused (400
HOOK_METHOD).
What a call is answered#
| Status | When | Body |
|---|---|---|
202 |
Started (async), a repeat of a call already started, or a sync call past its wait |
{"execution", "state", "deduplicated"} |
200 |
A sync call whose execution succeeded |
The workflow's outputs, as a JSON object |
502 WORKFLOW_FAILED |
A sync call whose execution failed or was cancelled |
{"code", "message", "execution"}: the message is the execution's reason |
400, 401, 403, 404, 429, 503 |
Refused | {"code", "message"}: see Troubleshooting |
Every answer that started an execution carries its name in the header
Astralyx-Execution. Look it up on the workflow's Executions tab, with
astra iris workflows get <execution>, or
GET $ASTRALYX_API/anemoi/flow-executions/<execution>.
The workflow as an API#
With mode: sync the call waits for the execution and is answered with
its outputs — a question in, an answer out, in one request:
spec:
triggers:
- {id: api, kind: http, mode: sync, wait_seconds: 60, auth: bearer}
outputs:
answer: "${{ steps.answer.outputs.answer }}"
- The call waits at most
wait_seconds: 60 s by default, at most 90. Past it, it is answered202with the execution, which goes on; look it up later. A model box waking a deployment at zero can take longer than that. - The
200body is the workflow'soutputs. An output whose text is a JSON object or list is sent as JSON; any other output is sent as text ("0.92", not0.92). - Make the workflow's boxes light ones where you can: a call waits for each of them.
See the recipe The workflow as an API.
Repeats and limits#
- Repeats. With
dedupe— a template over the event, such as${{ event.id }}— calls with the same key withindedupe_seconds(60 s by default, at most a day) start one execution. The later calls are answered202with that execution and"deduplicated": true. A key that renders empty (the event has noid) is not remembered. - Calls a minute.
per_minute, 60 by default, at most 600: the calls answered without an error in the last minute are counted, and a call beyond is answered429 HOOK_RATE_LIMITED. Retry it after a moment. - Places. With the workflow's
concurrency, an execution past the limit waits its turn; withwhen_full: dropthe call is refused,429 FLOW_BUSY.
Its last calls#
Triggers shows, under each address, who made it, when it was last called and that call's status. Last calls lists the latest 100: when, the status (with repeat for a deduplicated call), the error, the execution and the caller's address.
$ curl -sS "https://console.astralyx.cloud/api/v1/orgs/<org>/workspaces/<workspace>/clusters/<cluster>/flow-hooks?flow=card-spending" \
-H "Authorization: Bearer $ASTRALYX_TOKEN"
$ curl -sS "https://console.astralyx.cloud/api/v1/orgs/<org>/workspaces/<workspace>/clusters/<cluster>/flow-hooks/<id>/calls" \
-H "Authorization: Bearer $ASTRALYX_TOKEN"
The first lists the addresses (id, flow, trigger, runs_as,
created_at, url without its secret, last_call_at,
last_status); the second an address's calls (at, status,
execution, source, error, deduplicated), newest first.
Calls are kept 30 days. What a call carried is never kept with them.
A new secret#
New secret on the Triggers tab (or the same POST …/flow-hooks
again) gives the address a new secret: the address's id stays, the old
secret stops working at once (401 HOOK_UNAUTHORIZED), and calls now run
as whoever made the new one. Remove the address (DELETE
…/flow-hooks/<id>) stops it for good (404 HOOK_NOT_FOUND); removing the
trigger from the workflow's current version stops it too (404
HOOK_TRIGGER_GONE).
Who it runs as#
An address's calls run as the person who made it (or last made a new
secret for it), with their access in the workspace — their assistant
memory, their notices. Each call checks again that they still reach the
workspace and may start the workflow (flows:trigger). Someone who left the
workspace, or lost the permission, takes the address's power with them:
calls answer 403 HOOK_OWNER_GONE or 403 HOOK_OWNER_NOT_ALLOWED until
someone who may makes a new secret.
Troubleshooting#
| Answer | Why | What to do |
|---|---|---|
401 HOOK_UNAUTHORIZED |
The call does not prove it may the way the trigger says: no secret, an old one, the wrong header, or a signature of other bytes. | Send the secret the trigger's auth asks for. Sign the exact body you send. |
403 HOOK_OWNER_GONE, 403 HOOK_OWNER_NOT_ALLOWED |
Whoever made the address no longer reaches the workspace, or may no longer start the workflow. | Someone who may presses New secret, and gives the new address out. |
404 HOOK_NOT_FOUND |
No such address: removed, or mistyped. | Make one on Triggers. |
404 HOOK_FLOW_GONE |
The workflow is not there any more (deleted). | Make the workflow again, then a new address. |
503 HOOK_CLUSTER_UNREACHABLE |
The workflow's cluster did not answer. | Try again shortly. |
404 HOOK_TRIGGER_GONE |
The workflow's current version has no HTTP trigger of that id. | Make a version with it current again. |
400 HOOK_BODY |
The body says JSON but is not. | Send JSON, or another content type. |
400 HOOK_METHOD |
A GET, and the trigger does not allow_get. |
POST, or allow GET. |
400 HOOK_BODY_TOO_LARGE |
Over 128 KiB. | Put the file on a drive and send its path. |
400 INVALID_FLOW |
The workflow refused to start: an input it requires has no default, or the event is over 128 KiB. The message says which. | Give the input a default. |
429 HOOK_RATE_LIMITED |
Over per_minute. |
Retry later, or raise per_minute (at most 600). |
429 FLOW_BUSY |
Every place of the workflow is taken and it drops what comes then. | Retry later, or raise concurrency.max. |
502 WORKFLOW_FAILED |
A sync call's execution failed. |
Open the execution named in the body. |