Skip to main content

Payload shape

Every webhook is an HTTP POST with Content-Type: application/json. The body shape depends on the event family — workflow run events carry data.workflow_run; case events carry data.case.

Workflow run events

workflow_run.completed and workflow_run.failed carry the run that fired them:
{
  "event": "workflow_run.completed",
  "timestamp": "2026-04-22T10:30:45Z",
  "data": {
    "workflow_run": {
      "id": 12345,
      "workflow_id": 67890,
      "status": "completed",
      "input": { "...": "the input the run was started with" },
      "output": { "...": "the workflow's final output" },
      "created_at": "2026-04-22T10:25:00Z",
      "updated_at": "2026-04-22T10:30:45Z"
    }
  }
}
FieldDescription
eventOne of workflow_run.completed or workflow_run.failed.
timestampWhen the event was dispatched, in ISO 8601 UTC.
data.workflow_run.idUnique ID of the workflow run. Use this for deduplication.
data.workflow_run.workflow_idThe workflow that produced this run. Use to filter if you only care about specific workflows.
data.workflow_run.statuscompleted or failed. Mirrors event but is convenient to switch on.
data.workflow_run.inputThe input payload the run was started with.
data.workflow_run.outputThe workflow’s final output. For failed runs this may include error details.
data.workflow_run.created_atWhen the run started (ISO 8601 UTC).
data.workflow_run.updated_atWhen the run reached its terminal state (ISO 8601 UTC).

Case events

case.closed and case.error carry the case whose status changed:
{
  "event": "case.closed",
  "timestamp": "2026-04-22T10:30:45Z",
  "data": {
    "case": {
      "id": 12345,
      "subject": "Acme Corp — renewal",
      "status": "closed",
      "workflow_id": 67890,
      "attributes": { "...": "the case's current attribute values" },
      "created_at": "2026-04-22T10:25:00Z",
      "updated_at": "2026-04-22T10:30:45Z"
    }
  }
}
FieldDescription
eventOne of case.closed or case.error.
timestampWhen the event was dispatched, in ISO 8601 UTC.
data.case.idUnique ID of the case. Use this for deduplication.
data.case.subjectThe case’s human-readable subject. Never contains sensitive data.
data.case.statusclosed or error. Mirrors event but is convenient to switch on.
data.case.workflow_idThe workflow that owns the case. Use to filter if you only care about specific ones.
data.case.attributesThe case’s current attribute values, as a flat { key: value } object.
data.case.created_atWhen the case was created (ISO 8601 UTC).
data.case.updated_atWhen the case last changed (ISO 8601 UTC).
For workflows with restricted data handling, any case attribute marked sensitive is delivered as "[redacted]"; the subject is always sent in the clear. See PHI & HIPAA.

Authentication

Champ applies the authentication configured on the destination’s REST API connection to every delivery:
  • BearerAuthorization: Bearer <token>
  • Basic — HTTP Basic credentials
  • Custom headers — applied as-is to every request
Configure this when you create the REST API connection used as the subscription’s Connection. See Add a REST API integration.
Champ does not currently sign webhook payloads — there is no HMAC or X-Signature header. Use bearer-token or basic auth on the destination connection to verify that a request actually came from Champ, and restrict your handler to that credential.

Retries

Failed deliveries retry automatically:
ParameterValue
Max attempts10
BackoffPolynomially increasing between attempts
SuccessAny HTTP status code in 200299
FailureAny non-2xx response, timeout, or error
Connect timeout10 seconds
Read timeout30 seconds
After 10 failed attempts, the event is marked failed and no further attempts are made. You can see which events failed and inspect the last response on the Webhooks page; there is no manual retry button today.

Handling events

Respond quickly

Return a 2xx response as soon as possible. Don’t do heavy work inside the request handler — enqueue the payload for background processing and acknowledge immediately. A slow handler risks hitting the 30-second read timeout and being retried as a failure.

Be idempotent

Because Champ retries until it gets a 2xx, the same payload can arrive more than once. Deduplicate on the resource id plus eventdata.workflow_run.id for run events, data.case.id for case events (a run could fire both completed and failed in rare recovery cases — treat them as distinct keys).

Switch on event type

app.post("/webhooks/champ", async (req, res) => {
  const { event, data } = req.body;

  // Acknowledge fast; do the real work asynchronously.
  res.sendStatus(200);

  // Run events carry data.workflow_run; case events carry data.case.
  const resource = data.workflow_run ?? data.case;
  await queue.enqueue({ event, resource });
});

Filter by workflow

Subscriptions are tenant-wide. If you only care about specific workflows, filter by the payload’s workflow_id (data.workflow_run.workflow_id or data.case.workflow_id) inside your handler rather than trying to scope the subscription itself.