📘 Public beta · Endpoints are stable; OpenAPI specs and SDKs ship monthly. See changelog →
Products
Orchestration
Webhooks

Orchestration · Webhooks

See Webhooks (global) → for signature, retry, idempotency.

Event catalog

EventFires whendata keys
workflow.execution.startedAn execution transitions from pending to runningexecutionId · workflowId · workflowSlug · inputs · externalId
workflow.execution.completedExecution succeededexecutionId · workflowSlug · status · output · completedAt
workflow.execution.failedExecution failedexecutionId · workflowSlug · failedStepId · errorMessage
workflow.execution.cancelledExecution was cancelledexecutionId · workflowSlug · reason · actorUserId
workflow.step.completedA single step succeeded (high-volume; off by default)executionId · stepId · nodeId · output
workflow.step.failedA single step failed (after retries)executionId · stepId · nodeId · errorMessage · attempts
workflow.human_approval_pendingExecution reached a human_approval nodeexecutionId · stepId · actionLabel · assignTo · data
webhook.testYou sent a test from the dashboardsource: "test"

To observe an approval resolution from outside the workflow, subscribe to workflow.execution.completed / workflow.execution.failed — the run resumes from human_approval and emits the terminal event when it finishes.

Example: workflow.execution.completed

{
  "id": "evt_01HXY...",
  "event": "workflow.execution.completed",
  "occurredAt": "2026-05-24T08:14:22.481Z",
  "orgId": "org_01HXY...",
  "data": {
    "executionId": "exe_01HXY...",
    "workflowId": "wkf_01HXY...",
    "workflowSlug": "indonesian-kyc-v1",
    "workflowVersion": 3,
    "status": "succeeded",
    "completedAt": "2026-05-24T08:14:22.481Z",
    "output": {
      "customerId": "cus_01HXY...",
      "kycLevel": "premium",
      "screeningResult": "no_match"
    },
    "externalId": "loan-app-2026-05-24-001"
  }
}

output is by convention the last step's output. Workflow authors can shape this with a final transform node if they want a cleaner public-facing payload.

Example: workflow.human_approval_pending

{
  "id": "evt_01HXZ...",
  "event": "workflow.human_approval_pending",
  "occurredAt": "...",
  "orgId": "...",
  "data": {
    "executionId": "exe_01HXY...",
    "workflowSlug": "indonesian-kyc-v1",
    "stepId": "stp_01HXY...",
    "actionLabel": "AML hit on customer cus_01HXY... — MLRO review required",
    "assignTo": "role:mlro",
    "data": {
      "customerName": "Budi Santoso",
      "screeningResults": [...]
    }
  }
}

Use this to fire a Slack notification, send an email to the MLRO, or page someone via PagerDuty.

Register a subscription

POST/api/webhooks
Auth · API keyScope · webhooks:write
{
  "url": "https://your-app.example.com/webhooks/orchestration",
  "events": [
    "workflow.execution.completed",
    "workflow.execution.failed",
    "workflow.human_approval_pending"
  ]
}

Response includes the signing secret once:

{
  "data": {
    "webhook": {
      "id": "whk_01HXY...",
      "url": "...",
      "events": [...],
      "secret": "qe_whsec_...",
      "active": true
    }
  }
}

Signature verification

Orchestration signs deliveries with HMAC-SHA256 of the raw JSON body using the per-subscription secret. The signature is sent as a hex string in X-QEOR-Signature (the QEOR- prefix is current; a suite-wide migration to X-Quantum-Signature is on the 2026 roadmap — accept both for forward compatibility).

POST /your-webhook HTTP/1.1
Content-Type:        application/json
User-Agent:          quantum-elixir-orchestration/webhook
X-QEOR-Event:        workflow.execution.completed
X-QEOR-Delivery:     whd_01HXY...
X-QEOR-Signature:    sha256=<hex of HMAC-SHA256(secret, raw_body)>
X-QEOR-Timestamp:    1748151262
X-QEOR-Attempt:      1

Verifying on the receiver side

import crypto from 'crypto';

function verify(rawBody: string, signatureHeader: string, secret: string): boolean {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(expected),
  );
}

Receivers MUST verify the signature against the raw request body before processing — the signature is computed over the bytes you receive, not the parsed object. Rotate the secret quarterly via POST /api/webhooks/{id}/rotate-secret.

Retry policy

Same as the rest of the suite — up to 5 attempts with exponential backoff:

AttemptDelay
1immediate
210s
330s
42m
510m