Skip to content

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 trigger flows:update.
  • The workflow's inputs all have defaults, or are not required: a call gives the event only.
  • curl (and openssl, for a signed call).

1. Add the trigger#

  1. On the workflow's page press Edit (new version).
  2. From the palette's Start group, drag HTTP call onto the graph (or click it).
  3. 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).
  4. 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.

wf.http_trigger("bank", auth="hmac", dedupe="${{ event.id }}", dedupe_seconds=600)

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#

  1. On the workflow's page, open Triggers.
  2. Under the trigger, press Make the address.
  3. Copy what is shown — it is shown only this once: the URL, and for bearer or hmac the Bearer token or Signing secret. Try it is a curl command 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.

$ curl -sS -X POST '<address>' -H 'Content-Type: application/json' \
    -d '{"id": "t1", "text": "COMPRA APROVADA R$ 250,00 PADARIA SAO JOAO"}'
{"execution":"card-spending-3f9a1c","state":"Pending","deduplicated":false}
$ curl -sS -X POST '<address>' -H 'Authorization: Bearer <secret>' \
    -H 'Content-Type: application/json' \
    -d '{"id": "t1", "text": "COMPRA APROVADA R$ 250,00 PADARIA SAO JOAO"}'

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 answered 202 with the execution, which goes on; look it up later. A model box waking a deployment at zero can take longer than that.
  • The 200 body is the workflow's outputs. An output whose text is a JSON object or list is sent as JSON; any other output is sent as text ("0.92", not 0.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 within dedupe_seconds (60 s by default, at most a day) start one execution. The later calls are answered 202 with that execution and "deduplicated": true. A key that renders empty (the event has no id) 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 answered 429 HOOK_RATE_LIMITED. Retry it after a moment.
  • Places. With the workflow's concurrency, an execution past the limit waits its turn; with when_full: drop the 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.