POST /api/v1/webhook-subscriptions/{subscriptionId}/verify
Sends a signed challenge to the destination, synchronously. If the destination
answers 2xx with the same challenge, the subscription becomes active and
starts receiving deliveries; otherwise it becomes validation_failed. Both
outcomes answer 200: read data.status to know which one happened.
| 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 | The subscription belongs to the key's organization |
| Participation | None |
| Resource state | Any status; a verification already in progress is not started twice |
| Parameter | Type | Required | Description |
|---|---|---|---|
subscriptionId | UUID | yes | Webhook subscription of the key's organization |
| Header | Required | Description |
|---|---|---|
x-api-key | yes | API key created for this organization |
Idempotency-Key | yes | Stable key for this command; a replay returns the first result |
X-Correlation-Id | no | Caller correlation id, returned or generated |
curl -X POST "$COHORTE_API_BASE_URL/api/v1/webhook-subscriptions/$SUBSCRIPTION_ID/verify" \
-H "x-api-key: $COHORTE_API_KEY" \
-H "Idempotency-Key: webhook-crm-sync-verify-001"The destination receives a signed POST whose body is:
{"type": "cohorte.webhook.challenge", "challenge": "opaque-value"}It must answer 2xx with a JSON body containing the same value:
{"challenge": "opaque-value"}200 OK — the verification ran; data.status is active on success or validation_failed on failure. replayed: true when the key was already used, or when a verification of this subscription is already in progress.400 Bad Request — invalid_request: missing Idempotency-Key.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.404 Not Found — not_found: the subscription is unknown or belongs to another organization.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 as scheme://host[:port]/path; the query string is never returned |
data.eventNames | string[] | no | Subscribed event names |
data.status | string | no | pending_validation, validating, active, paused or validation_failed |
data.lastValidatedAt | ISO 8601 string | yes | Last successful verification, in UTC |
data.lastValidationError | string | yes | Why the last verification failed |
data.secretConfigured | boolean | no | Always true; the secret itself is never returned |
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": "active",
"lastValidatedAt": "2026-09-20T09:12:04.000Z",
"lastValidationError": null,
"secretConfigured": true,
"createdAt": "2026-09-20T09:10:00.000Z",
"updatedAt": "2026-09-20T09:12:04.000Z"
},
"correlationId": "b7d3e1f0-2a4c-4e8b-9c1d-0f2e3a4b5c6d"
}A failed verification looks like this:
{
"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": "validation_failed",
"lastValidatedAt": null,
"lastValidationError": "La destination n’a pas confirmé le challenge.",
"secretConfigured": true,
"createdAt": "2026-09-20T09:10:00.000Z",
"updatedAt": "2026-09-20T09:12:04.000Z"
},
"correlationId": "b7d3e1f0-2a4c-4e8b-9c1d-0f2e3a4b5c6d"
}x-cohorte-event-id header set to the subscription id. See
verify the signature.lastValidationError is a short human-readable reason, currently written in
French. Do not branch on its text.Idempotency-Key returns the recorded outcome without
calling the destination again. Use a new key to verify again after fixing the
destination. Each completed verification is recorded in the organization's
audit trail.