Skip to main content
Platform webhooks push lifecycle events to your platform as signed HTTPS POSTs. All endpoint management requires the webhooks:manage scope.

Registering an Endpoint

  • url must be https (plain http is allowed only for localhost during development). Redirects are not followed — deliveries to a redirecting URL fail.
  • eventTypes is 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 secret is 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 /contracts to reconcile anything earlier.

Anatomy of a Delivery

Each event is POSTed to your URL with Content-Type: application/json and:
HeaderContents
X-OpenTrain-EventThe event type (e.g. contract.started)
X-OpenTrain-DeliveryUnique delivery ID — dedupe on it; retries reuse the same ID
X-OpenTrain-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<rawBody>")>
User-AgentOpenTrain-Partner-Webhooks/1.0
The body is one event record. Verify the signature against the raw request bytes before parsing — the scheme is identical to the Public API’s; the signature verification guide has tested Node and Python implementations. Respond with any 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 marked FAILED 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: its status 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:
Then run a pull-side reconciliation against 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 FAILED deliveries for the endpoint.
Requeued deliveries are only attempted while the endpoint is ACTIVE.

Managing Endpoints

The secret is never returned by 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 2xx within 10 seconds; queue slow work
  • Return 5xx (or any non-2xx) when processing genuinely fails, so OpenTrain retries
  • Monitor consecutiveFailures via GET /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