Skip to main content
Status: Production partner preview for admitted personal tenants. Projection starts with events created after activation and does not backfill earlier task events.
Webhooks let your server receive durable run progress without holding an SSE connection open. Praxa signs every request, retries failed deliveries, and may deliver the same event more than once or out of order. Your handler must verify the raw request, persist each event idempotently, and order changes within each run.

1. Register an endpoint

The signed-in developer portal uses POST /api/platform/webhooks. The bearer SDK contract uses this active partner-preview operation:
This operation is available only to keys issued to admitted personal tenants. It is not generally available.
The gateway requires runs:write. The backend reauthorizes both runs:read and runs:write, so the key must carry both scopes. The response status is 201. The response includes signing_secret once. Store it immediately in your secret manager; Praxa will not show it again.
The automatic projection migration is forward-only. An endpoint receives matching events created after the production projection migration; creating an endpoint does not backfill earlier run history.

2. Verify the signature before parsing

Every delivery carries:
Praxa computes HMAC-SHA-256 over:
Verify the exact raw bytes. Parsing and re-serializing JSON first can change whitespace or key order and invalidate the signature.
Use it before JSON.parse:
persistOnceAndQueue should atomically insert event_id under a unique constraint and enqueue any slower work. Return 2xx only after that transaction commits. If the id already exists, acknowledge the duplicate without repeating its effects.

3. Deduplicate and order per run

Praxa provides at-least-once delivery. Retries and manual replay preserve the same immutable event_id, so use that as the deduplication key. Delivery order is not guaranteed. A retry of sequence 11 can arrive after sequence 12. Use (run_id, sequence) to process or reconstruct one run’s ordered history:
Do not treat sequence as global, and do not use HTTP arrival order as run state. You may receive a later event before an earlier one.

4. Parse the strict v1 body

The body contains exactly seven top-level fields:
The data object is strict and depends on type. Reconciliation notifications are run.progress with exactly { "status": "running" }. A permanently unknown committing outcome is:
Do not automatically retry or resubmit that run. See Webhooks for every type-specific shape.

5. Fetch the approval before rendering it

An approval.required webhook is a notification, not the approval presentation. Its data contains only:
Fetch GET /v1/runs/:id, require pendingApproval, and verify its digest equals the webhook digest. Only then render the exact pendingApproval.summary and allow a human to approve or deny. If the run no longer exposes that exact pending approval, fail closed and acknowledge the webhook without showing active decision controls. Submit the public decision with the unchanged digest:
The public request accepts approve or deny. The later approval.resolved.data.decision webhook deliberately preserves the internal event values approved or denied. Do not send those past-tense values in the public approval request.

6. Understand retries and dead letters

  • Return 2xx after durable acceptance, then process slow work asynchronously.
  • Timeouts, 408, 409, 425, 429, and 5xx responses are retryable.
  • Praxa makes at most five attempts, delayed by 30 seconds, 5 minutes, 30 minutes, and 2 hours.
  • Other 4xx responses and refused or unsafe redirects fail permanently.
  • A delivery that exhausts retries enters dead_letter. Inspect it and use the idempotent replay action after correcting your endpoint.
Use the active bearer management route to list delivery records with a key carrying runs:read:
You can filter by endpoint_id, run_id, or status, page with cursor, and set limit from 1 to 100. The default is 50. The response status is 200, and the strict list contains apiVersion, data, and optional nextCursor. Replay a corrected dead-letter delivery by sending an exact empty object:
The gateway checks runs:write, while the backend requires both runs:read and runs:write. A successful replay returns 202 and the created or idempotently reused delivery record.

Production evidence

A production canary created an endpoint, received a delivery, and verified its HMAC over the exact raw body. Retry, dead-letter, eligible replay, and redirect safety remain separate operational canaries. Build idempotent handling as documented even when a particular retry path has not been forced in a live canary.

Next steps

  • Build the human decision flow with Build an approval UI.
  • Review the full payload, signing, activation, and endpoint constraints in Webhooks.
Last modified on August 14, 2026