POST /v1/webhooks
Subscribe a URL to events
Subscribe a URL to events
Creates a subscription for deal.stage_changed, payment.received, document.signed, lead.created and shipment.updated. The response contains the signing secret ONCE. Not idempotent by design (the secret must not be stored for replay): list subscriptions before retrying. At most 20 subscriptions per tenant. Requires scope write:deals. Every delivery is a POST with a JSON body and the headers X-Signature: t=<unix-seconds>,v1=<hex> and X-Event-Id: <uuid>. v1 = hex(HMAC_SHA256(secret, ${t}.${rawBody})) where secret is the whsec_... value returned when the subscription was created and rawBody is the exact request body bytes (do not re-serialize the JSON). Reject the request if |now - t| > 300 s (replay protection) or if no v1 value matches (compare in constant time). During secret rotation several v1= values can be present: accept if any matches. Answer with any 2xx within 8 seconds. Other statuses, timeouts and network errors are retried with exponential backoff (1, 2, 4 ... minutes, up to 6 attempts) and then marked dead; X-Event-Id (= body.id) is stable across retries: use it to deduplicate. Only https URLs on public hosts are accepted. The body is { id, type, tenantId, createdAt, data } (plus informational fields); data carries identifiers and public fields only (no margins, no personal data). A ready-made verifier is exported by the SDK: import { verifyWebhookSignature } from "@importos/sdk/webhooks".
- Authentication: header
Authorization: Bearer rv_live_...(API keys and scopes) - Required scope:
write:deals
openapi.json).Request body
| Field | Type | Required | Description |
|---|---|---|---|
url | string (uri) | yes | HTTPS endpoint that receives POST requests. Private/internal hosts are rejected. |
events | "deal.stage_changed" | "payment.received" | "document.signed" | "lead.created" | "shipment.updated"[] | yes | Events to receive. |
description | string | null | no |
Responses
201
The subscription and its secret (shown once).
application/json · Schema: WebhookCreated
| Field | Type | Required | Description |
|---|---|---|---|
webhook | object | yes | |
webhook.id | string (uuid) | yes | |
webhook.url | string | yes | |
webhook.description | string | null | yes | |
webhook.events | string[] | yes | |
webhook.enabled | boolean | yes | |
webhook.secretPrefix | string | null | yes | First characters of the signing secret, to tell secrets apart. The full secret is never shown again. |
webhook.failureCount | integer | yes | |
webhook.lastSuccessAt | string | null | yes | ISO 8601 date-time (UTC). |
webhook.lastFailureAt | string | null | yes | ISO 8601 date-time (UTC). |
secret | string | yes | Signing secret (whsec_...). Shown ONCE, store it now. Use it to verify X-Signature. |
400
Invalid request parameters or body (code validation_error).
application/problem+json · Schema: Problem
401
Missing, malformed, unknown, revoked or expired API key (code unauthorized).
application/problem+json · Schema: Problem
403
The API key lacks the scope required by this endpoint (code insufficient_scope).
application/problem+json · Schema: Problem
409
Subscription limit reached (webhook_limit).
application/problem+json · Schema: Problem
422
The URL is not acceptable: not https, private host, credentials in URL (webhook_url_rejected).
application/problem+json · Schema: Problem
429
Rate limit exceeded for the tenant plan (code rate_limited). Wait Retry-After seconds.
application/problem+json · Schema: Problem