webhooks:manage scope.
Registering an Endpoint
urlmust be https (plainhttpis allowed only forlocalhostduring development). Redirects are not followed — deliveries to a redirecting URL fail.eventTypesis a non-empty subset of the eight-event catalog:contract.started,contract.ended,project_link.created,project_link.removed,install.revoked,milestone.funded,milestone.budget_low,milestone.budget_depleted.- The signing
secretis returned once, in the creation response only. It cannot be retrieved later — if lost, delete the endpoint and create a new one (which mints a new secret). - There is no backfill. A new endpoint starts at the current event high-water mark and only receives events created after registration. Use
GET /contractsto reconcile anything earlier.
Anatomy of a Delivery
Each event is POSTed to your URL withContent-Type: application/json and:
| Header | Contents |
|---|---|
X-OpenTrain-Event | The event type (e.g. contract.started) |
X-OpenTrain-Delivery | Unique delivery ID — dedupe on it; retries reuse the same ID |
X-OpenTrain-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<rawBody>")> |
User-Agent | OpenTrain-Partner-Webhooks/1.0 |
2xx within 10 seconds. Do the actual work asynchronously if it might run long; a timeout counts as a failure.
Retries
A failed delivery (non-2xx, timeout, or connection error) is retried up to 5 total attempts with backoff delays of 1, 5, 30, and 120 minutes after the first failure. A delivery that exhausts all 5 attempts is markedFAILED and can be requeued manually with redeliver.
Because retries reuse the same X-OpenTrain-Delivery ID, idempotent handling is simple: record the IDs you have processed and return 200 immediately for repeats.
Auto-Disable and Recovery
After 10 consecutive failed deliveries, the endpoint is auto-disabled: itsstatus flips to DISABLED (with disabledAt and disabledReason set), all its PENDING deliveries are marked FAILED, and no new deliveries are attempted. A successful delivery at any point resets consecutiveFailures to 0.
To recover:
GET /contracts for anything emitted before the endpoint existed or after deliveries were discarded.
Redelivery
POST /webhook-endpoints/{endpointId}/redeliver:
- With
{"deliveryId": "..."}— requeues that specific delivery regardless of its status (useful for replaying one event into a fixed handler). - Without a body — requeues all
FAILEDdeliveries for the endpoint.
ACTIVE.
Managing Endpoints
GET or PATCH. Changing the URL via PATCH keeps the existing secret; only delete + create mints a new one.
Operations Checklist
- Verify the signature on raw bytes with a constant-time compare, and reject stale timestamps (guide)
- Dedupe by
X-OpenTrain-Delivery - Return
2xxwithin 10 seconds; queue slow work - Return
5xx(or any non-2xx) when processing genuinely fails, so OpenTrain retries - Monitor
consecutiveFailuresviaGET /webhook-endpoints/{endpointId}and alert before it reaches 10 - On recovery:
PATCH {"status": "ACTIVE"}→ redeliver → pull-side reconcile - Store the secret in a secrets manager at creation time — it is shown exactly once