Skip to content

Triggers

A trigger node is how a canvas graph declares the events that start it. It’s the inbound side of a graph: emit nodes fire events out, trigger nodes bring events in. On publish, a graph’s trigger nodes register the graph as an event handler, so the same wiring you’d set up with cryo handler set happens from the canvas.

A trigger node has a transport that says how the world reaches it:

  • bus (the default) subscribes to the project event bus. The graph runs whenever a matching event fires - a finished run, a cryo emit, a delivery another hook republished.
  • webhook gives the graph its own inbound HTTP endpoint. Publishing the graph mints the URL and a signing secret; a POST to that URL starts the graph. Use it to catch a forge push, or any external system, directly.

A trigger with no transport is a bus trigger, so graphs authored before webhook triggers existed keep working unchanged.

A bus trigger names the event pattern that starts the run:

{ "id": "on_failure", "type": "trigger",
"config": { "event": "run.failed" } }

The event value is an event-type pattern, the same kind a handler’s on list takes:

  • an exact type - run.failed, deploy-done
  • a prefix wildcard - run.* matches run.succeeded, run.failed, run.cancelled
  • * - every event

A webhook trigger owns an endpoint the graph provisions on publish:

{ "id": "on_push", "type": "trigger",
"config": { "transport": "webhook", "manifest": "github", "event": "push" } }
  • manifest (optional) selects signed-delivery verification and payload shaping for a known sender. github verifies the X-Hub-Signature-256 HMAC and normalizes the push into fields like sha and branch. Leave it off for a plain token-secret webhook.
  • event (optional) filters which delivery subtype starts the run (push, pull_request). Empty runs on any delivery.

The endpoint is an ordinary event hook underneath, named graph.<graph>.<node>, and its deliveries reach the bus like any other hook’s. What differs is who owns it: that section has the three things that decide which to reach for.

One consequence of the endpoint being keyed to the node: renaming the trigger node re-mints it. Publishing after a rename drops graph.<graph>.<old-id> and creates graph.<graph>.<new-id> with a new URL and a new secret, so whatever was posting to the old one is now posting nowhere. Re-register the sender, or leave a live trigger node’s id alone.

Publishing the graph mints the endpoint; the trigger inspector shows the ingest URL, a one-time secret, and a Register on forge control that creates the webhook on the repo for you (GitHub, Gitea, or Forgejo) given a repo and a forge admin credential - no manual paste. Or add it to your repo’s webhook settings by hand with the shown URL + secret. The secret is shown once - a re-publish keeps the same one. A token trigger’s secret can be replaced from the Connections page (rotate secret) or with cryo event-hook rotate <name>; a manifest trigger signs with a key instead, which is replaced by deleting the hook and publishing again. CI from a repo walks through the whole push-to-build path.

The node’s output is the event, or shaped delivery, that started the run. Downstream nodes read it through nodes.<trigger-id>.output, and because a graph receives the triggering event as its run input, they can read the same envelope through input. A run.failed bus trigger reads input.payload.error, input.payload.run_id, and the rest of the lifecycle payload; a github webhook trigger reads input.sha, input.ref, and the other shaped fields (repo, sender).

A webhook trigger with no manifest is not shaped, so it hands you the delivery as it arrived:

{ "body": { "sku": "TENT-2P", "qty": 12 }, "query": { "source": "shop" } }

Read a posted field as input.payload.body.sku and a query-string one as input.payload.query.source - not input.sku, which is the shaped github case above and is the mistake this shape invites. Query values are always strings; body values keep their JSON types. A POST with no body at all arrives as an empty object, so input.payload.body.sku finds nothing rather than failing; a body that is not JSON (form-encoded, plain text) arrives verbatim as a string, so guard when you expect one.

The sender authenticates with the secret in the X-Cryo-Hook-Secret header - see events and handlers for a worked curl.

When you publish a graph, its trigger nodes reconcile into an event-handler registration named after the graph. The handler’s on list is the union of every trigger node’s event. Publish a graph with two bus trigger nodes (run.failed and deploy-done) and it registers as a graph handler matching both.

This is the canvas-native equivalent of:

Terminal window
cryo handler set <graph> --graph <graph> --on run.failed,deploy-done

A webhook trigger also provisions its owned endpoint at this point and subscribes the graph to the event that endpoint emits, so there’s no separate hook or handler to set up - the trigger node is the whole ingress.

Remove all the trigger nodes and republish, and the registration (and any owned webhook) is dropped - the graph goes back to being a workflow you run by hand or spawn, with no event subscription.

A notify graph wires one trigger to one connector node. The trigger starts the graph on any failed run; the connector posts the error to Matrix:

{
"version": "cryosleep-graph/v1",
"nodes": [
{ "id": "on_failure", "type": "trigger",
"config": { "event": "run.failed" } },
{ "id": "post", "type": "matrix.send_message",
"config": {
"with": { "homeserver": "https://matrix.example",
"room_id": "!abc:example" },
"input": { "text": { "$template": "run failed: {{ input.payload.error }}" } },
"credential": "matrix"
} }
],
"edges": [ { "from": "on_failure", "to": "post" } ]
}

Publish it, and every run.failed on the project bus posts to the room. No separate subscription to set up - the trigger node is the subscription.

  • Connectors - the outbound API call the example uses.
  • Events and handlers - what handlers are, what a graph handler receives, and how emit nodes fire events out.
  • Event catalog - discovering which events you can put in a trigger node.