Payload shape
Every webhook is an HTTP POST with Content-Type: application/json and this body:
{
"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"
}
}
}
| Field | Description |
|---|
event | One of workflow_run.completed or workflow_run.failed. |
timestamp | When the event was dispatched, in ISO 8601 UTC. |
data.workflow_run.id | Unique ID of the workflow run. Use this for deduplication. |
data.workflow_run.workflow_id | The workflow that produced this run. Use to filter if you only care about specific workflows. |
data.workflow_run.status | completed or failed. Mirrors event but is convenient to switch on. |
data.workflow_run.input | The input payload the run was started with. |
data.workflow_run.output | The workflow’s final output. For failed runs this may include error details. |
data.workflow_run.created_at | When the run started (ISO 8601 UTC). |
data.workflow_run.updated_at | When the run reached its terminal state (ISO 8601 UTC). |
Authentication
Champ applies the authentication configured on the destination’s REST API connection to every delivery:
- Bearer —
Authorization: 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:
| Parameter | Value |
|---|
| Max attempts | 10 |
| Backoff | Polynomially increasing between attempts |
| Success | Any HTTP status code in 200–299 |
| Failure | Any non-2xx response, timeout, or error |
| Connect timeout | 10 seconds |
| Read timeout | 30 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 data.workflow_run.id plus event (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;
const run = data.workflow_run;
// Acknowledge fast; do the real work asynchronously.
res.sendStatus(200);
await queue.enqueue({ event, run });
});
Filter by workflow
Subscriptions are tenant-wide. If you only care about specific workflows, filter by data.workflow_run.workflow_id inside your handler rather than trying to scope the subscription itself.