POST /api/v1/webhook-subscriptions
Registers a destination that will receive the chosen events, signed with the
secret you provide. A new subscription starts in pending_validation and
receives nothing until verification
succeeds.
| Role in the organization | This endpoint |
|---|---|
OWNER | Allowed |
ADMIN | Allowed |
MEMBER | Refused — 403 |
| Requirement | Rule for this endpoint |
|---|---|
| Business role | None beyond the organization role |
| Resource scope | Organization from the API key |
| Participation | None |
| Resource state | New subscriptions always start in pending_validation |
| Header | Required | Description |
|---|---|---|
x-api-key | yes | API key created for this organization |
Idempotency-Key | yes | Stable key for this creation; a replay returns the first result |
X-Correlation-Id | no | Caller correlation id, returned or generated |
| Field | Type | Required | Nullable | Description |
|---|---|---|---|---|
name | string | yes | no | 2 to 120 characters, trimmed |
endpointUrl | string | yes | no | Absolute URL, up to 2000 characters; must be https:// on a public host name (see Notes) |
secret | string | yes | no | 16 to 512 characters; used to sign every request, never returned |
eventNames | string[] | yes | no | At least one supported event name |
{
"name": "CRM sync",
"endpointUrl": "https://hooks.example.com/cohorte?tenant=acme",
"secret": "whsec_7f3c9a1e5b2d4c6f8a0b",
"eventNames": ["enrollment.created", "enrollment.closed"]
}curl -X POST "$COHORTE_API_BASE_URL/api/v1/webhook-subscriptions" \
-H "x-api-key: $COHORTE_API_KEY" \
-H "Idempotency-Key: webhook-crm-sync-001" \
-H "Content-Type: application/json" \
-d '{
"name": "CRM sync",
"endpointUrl": "https://hooks.example.com/cohorte?tenant=acme",
"secret": "whsec_7f3c9a1e5b2d4c6f8a0b",
"eventNames": ["enrollment.created", "enrollment.closed"]
}'201 Created — the subscription was created, replayed: false.200 OK — the same Idempotency-Key replayed the original creation, replayed: true.400 Bad Request — invalid_request: missing Idempotency-Key, invalid JSON, or a field out of bounds; details names the fields, or endpointUrl that is not a public https:// address without credentials.401 Unauthorized — unauthorized: the key is missing, invalid, expired or no longer attached to a member.403 Forbidden — forbidden: the bearer is not OWNER or ADMIN.500 Internal Server Error — internal_error: unexpected failure.| Field | Type | Nullable | Description |
|---|---|---|---|
success | boolean | no | true for a successful request |
replayed | boolean | no | true when the idempotency key was already used |
data.id | UUID | no | Subscription identifier |
data.name | string | no | Display name |
data.endpointUrl | string | no | Destination without its query string |
data.eventNames | string[] | no | Subscribed event names |
data.status | string | no | pending_validation |
data.lastValidatedAt | ISO 8601 string | yes | null until verified |
data.lastValidationError | string | yes | null on creation |
data.secretConfigured | boolean | no | Always true |
data.createdAt | ISO 8601 string | no | Creation time, in UTC |
data.updatedAt | ISO 8601 string | no | Last change, in UTC |
correlationId | string | no | Correlation id for support |
{
"success": true,
"replayed": false,
"data": {
"id": "6b1e2c3d-4f50-4a61-8b72-93a4b5c6d7e8",
"name": "CRM sync",
"endpointUrl": "https://hooks.example.com/cohorte",
"eventNames": ["enrollment.created", "enrollment.closed"],
"status": "pending_validation",
"lastValidatedAt": null,
"lastValidationError": null,
"secretConfigured": true,
"createdAt": "2026-09-20T09:10:00.000Z",
"updatedAt": "2026-09-20T09:10:00.000Z"
},
"correlationId": "b7d3e1f0-2a4c-4e8b-9c1d-0f2e3a4b5c6d"
}https://, carry no user name, password or
fragment, use a host name rather than an IP address, and resolve only to
public addresses. Private, loopback, link-local and reserved networks are
refused, at creation and again at every verification and delivery.active are queued for
it and sent once it is verified; a paused subscription receives no new
deliveries.Idempotency-Key returns the first subscription and
creates no second one. The creation is recorded in the organization's audit
trail.