Skip to content

Workflow Webhook Triggers

Workflow webhook triggers let external systems fire a Z.E.N. workflow with a signed JSON event.

YAML

yaml
name: transcript-ingest
description: Ingest transcript readiness events
triggers:
  webhook:
    - event: meeting.transcript.ready
      slug: meeting-transcript-ready
      secret_env: CADENCE_WEBHOOK_SECRET
      idempotency_key: transcript_id
      dedupe_window_sec: 86400
      filter:
        source: cadence-core
nodes:
  - id: ingest
    bash: echo "$TRIGGER_PAYLOAD"

The public endpoint is:

text
POST /webhooks/workflow/:slug

If you run Z.E.N. behind Cloudflare Tunnel, ngrok, or another HTTPS tunnel, point the producer at the public tunnel origin plus that path. Z.E.N. still verifies the raw body signature locally.

Signature

The producer signs the exact raw JSON body with HMAC-SHA256. Do not canonicalize or reformat JSON between signing and sending.

text
x-zen-signature-256: sha256=<hex hmac>

secret_env names an environment variable, not a literal secret. Z.E.N. looks it up first in workspace env vars, then in process env.

Request

bash
body='{"event":"meeting.transcript.ready","transcript_id":"tx-123","source":"cadence-core","store_ref":"sqlite|path|url"}'
sig="$(printf '%s' "$body" | openssl dgst -sha256 -hmac "$CADENCE_WEBHOOK_SECRET" -hex | awk '{print $2}')"

curl -X POST "https://zen.example.com/webhooks/workflow/meeting-transcript-ready" \
  -H "content-type: application/json" \
  -H "x-zen-signature-256: sha256=$sig" \
  --data "$body"

Responses

StatusBodyMeaning
202{ "accepted": true, "run_id": "..." }Accepted and workflow run created
200{ "skipped": "duplicate", ... }Duplicate retry inside dedupe window
200{ "skipped": "event_mismatch" }Signature valid, event ignored
200{ "skipped": "filter_mismatch" }Signature valid, filter ignored
400{ "error": "invalid_idempotency_key" }Missing or non-scalar key
401{ "error": "invalid_signature" }Missing or invalid HMAC
404{ "error": "webhook_trigger_not_found" }No active trigger for slug
409{ "error": "idempotency_key_conflict" }Same key, different payload
409{ "error": "workflow_not_available" }Trigger row points at removed workflow
413{ "error": "payload_too_large" }Body exceeds 256 KiB
503{ "error": "webhook_secret_missing" }secret_env is not configured

Webhook runs receive these environment variables:

Env varValue
TRIGGER_TYPEwebhook
TRIGGER_PAYLOADRaw JSON payload string
WEBHOOK_EVENTMatched event name
WEBHOOK_IDEMPOTENCY_KEYExtracted idempotency key
WEBHOOK_RECEIVED_ATISO timestamp when Z.E.N. accepted payload

AI that follows a recipe, not a conversation.