Outbound webhooks
Outbound webhooks notify your HTTPS receiver when a root workflow execution reaches a terminal
state. They replace polling for integrations that need a reliable completion signal; polling and
GET /api/v1/executions/{id} remain available as the recovery/read path.
Setup flow
Section titled “Setup flow”- Create an endpoint with
POST /api/v1/webhooks/endpointsand save the returnedsigning_secret. It is shown only once. - Verify signatures against the exact raw request bytes.
- Send a signed test with
POST .../{endpoint_id}/testand inspect its delivery. - Activate the endpoint with
POST .../{endpoint_id}/activate.
Changing the destination with PATCH .../{endpoint_id} creates a new immutable URL revision,
returns the endpoint to pending_verification, and suspends pending deliveries. Send a new signed
test to that URL before activating again; a test against an older revision does not satisfy the gate.
All mutations require an Idempotency-Key header (8–255 characters). Repeating the same request
does not create another endpoint, secret, test, retry, or replay. A replayed create/rotation never
shows the secret again. State-setting operations are also safe when the requested state has already
been reached: activating an active endpoint, pausing a paused endpoint, or archiving an archived
endpoint succeeds as a no-op.
curl -X POST "$BASE_URL/api/v1/webhooks/endpoints" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: endpoint-prod-$(uuidgen)" \ -d '{ "name":"Production receiver", "url":"https://example.com/madoo-webhooks", "workflow_scope":"selected", "workflow_ids":["wf_53fb2f6576fa4bc9a6d3c91a7e84de47"] }'The URL must be public HTTPS on port 443. Madoo rejects private/link-local addresses, redirects, embedded credentials and hosts that resolve to a non-public address. DNS is checked again and the resolved IP is pinned on every attempt.
Signature verification
Section titled “Signature verification”Madoo uses the Standard Webhooks HMAC-SHA256 convention. Requests include:
webhook-id: stable event ID, also stable across retry/replay;webhook-timestamp: Unix seconds;webhook-signature: one or two space-separatedv1,<base64>signatures during rotation.
The signed bytes are:
webhook-id + "." + webhook-timestamp + "." + raw_request_bodyRemove whsec_ from the show-once secret and Base64-decode the remainder before computing HMAC.
Accept a timestamp difference of at most five minutes, compare signatures in constant time, and
deduplicate webhook-id before applying side effects. Never parse and re-serialize the JSON before
verification.
An executable Node receiver is in
docs/demo-workflows/webhook-completion-receiver.
Event and results
Section titled “Event and results”The request is a CloudEvents 1.0 structured event:
com.madoo.execution.finished.v1forcompleted,failed,cancelled, orpartial_success;com.madoo.webhook.test.v1for a verification test.
Small text/JSON outputs may be included inline. Media are always references, never bytes. Every
completion event contains result_url, which is the authoritative result read path, and trace_url,
which explains how the run executed without embedding a potentially large trace. If the 20 KiB
event budget is reached, outputs_truncated is true and the remaining outputs are available from
that URL.
Delivery behavior
Section titled “Delivery behavior”Any 2xx response succeeds. Redirects are not followed. Failures retry with durable exponential
backoff until the configured deadline; Retry-After is honored. A real delivery receiving 410
disables the endpoint. Verification tests make one attempt only and never disable the endpoint.
The Webhook page and REST API expose each delivery and its attempts. retry is for an exhausted
delivery; replay resends a delivered or exhausted delivery with the same event body and
webhook-id, but a fresh timestamp/signature. A correctly idempotent receiver may ignore a replay
whose event was already processed.
REST v1 routes
Section titled “REST v1 routes”GET/POST /api/v1/webhooks/endpointsGET /api/v1/webhooks/endpoints/{endpoint_id}PATCH /api/v1/webhooks/endpoints/{endpoint_id}POST /api/v1/webhooks/endpoints/{endpoint_id}/testPOST /api/v1/webhooks/endpoints/{endpoint_id}/activatePOST /api/v1/webhooks/endpoints/{endpoint_id}/pausePOST /api/v1/webhooks/endpoints/{endpoint_id}/rotate-secretPOST /api/v1/webhooks/endpoints/{endpoint_id}/revoke-previous-secretDELETE /api/v1/webhooks/endpoints/{endpoint_id}GET /api/v1/webhooks/deliveries[/{delivery_id}]POST /api/v1/webhooks/deliveries/{delivery_id}/retryPOST /api/v1/webhooks/deliveries/{delivery_id}/replayGET /api/v1/webhooks/activityUse webhooks:read for read routes and webhooks:write plus the workspace webhook-management
permission for mutations. MCP exposes the secret-free mutations and marks them as destructive so a
client can require explicit confirmation; its OAuth consent text includes destination URL changes.
The AI Agent and the in-editor AI Assistant expose webhook status in read-only mode until Madoo has
a dedicated human-approval flow for webhook mutations. Create/rotate operations that reveal a
secret remain REST/UI-only.