Skip to content
OpenTrain AIOpenTrain AIOpenTrain AIDocs

Ask OpenTrain

Answers from the documentation, with sources.

What would you like to do with OpenTrain?

AI answers can be mistaken. Check the linked sources. Don’t include private account information.

Open app

DEVELOPER DOCUMENTATION

Create Webhook

Subscribe an HTTPS endpoint to account events. The HMAC signing secret is returned once — store it immediately.

POST/api/public/v1/webhooks

Subscribes a URL to platform events — push delivery of the same events GET /updates serves. Each delivery is signed with HMAC-SHA256 via the X-OpenTrain-Signature header; verify it with the signature guide.

The secret is returned only in this response. It cannot be retrieved later — if lost, delete the subscription and create a new one. There is no backfill: a new subscription starts at the current event high-water mark and only receives events created after it; older events remain reachable via GET /updates.

Accounts can hold at most 10 subscriptions. For the polling-vs-webhooks decision framework and the full delivery lifecycle, see stay in sync.

Requirements: webhooks:manage scope + the public_api_webhooks feature + the matching read scope for every subscribed event type (e.g. proposal.received needs proposals:read).

urlstringbodyrequired

Absolute https URL that will receive deliveries (max 2048 chars). Plain http is allowed only for localhost during development.

eventTypesstring[]bodyrequired

Non-empty array of event types from the event catalog: proposal.received, proposal.status_changed, message.received, contract.created, milestone.status_changed, payment.pending, approval.confirmed, contract.budget_state_changed. Duplicates are de-duplicated.

webhookobject

The subscription: {id, url, eventTypes, status: "ACTIVE", createdAt, disabledAt, disabledReason} — same shape as GET /webhooks/{id}.

secretstring

The whsec_… signing secret. Shown once — store it now.

messagestring

Reminds you to store the secret.

Each event is a POST to your URL with Content-Type: application/json, User-Agent: OpenTrain-Webhooks/1.0, and headers X-OpenTrain-Event (the event type), X-OpenTrain-Delivery (unique delivery ID — dedupe on it), and X-OpenTrain-Signature (t=<unix seconds>,v1=<hex HMAC>). The body is exactly one /updates event record — IDs only, never content.

Respond with any 2xx within 10 seconds. Failures retry up to 5 attempts with backoff (1m, 5m, 30m, 120m); after 10 consecutive exhausted deliveries the subscription is auto-disabled — recover by deleting and re-creating it.

StatuscodeMeaning
400BAD_REQUESTBody not valid JSON; url missing/invalid/not https (details.field: "url"); eventTypes empty or contains an unsupported type (details.supportedEventTypes)
401UNAUTHORIZEDMissing or invalid token
403FORBIDDENMissing webhooks:manage, missing a subscribed event type’s read scope, or public_api_webhooks disabled
409CONFLICTSubscription limit reached (details: {limit: 10}) — delete an existing webhook first