Webhooks
Receive event notifications via HTTPS webhooks
Webhooks let you receive monitoring events automatically instead of polling the events API. When an event is detected on a watched company, Verifiables sends an HTTPS POST request to your endpoint with the event payload, signed with HMAC-SHA256. Deliveries are fanned out from a queue that runs every minute.
Webhook lifecycle
- Register a webhook URL. You receive a
secretfor signature verification. - Events fire and Verifiables delivers them to your endpoint.
- Verify the HMAC signature on each delivery.
- Respond with a
2xxstatus within 10 seconds. - On failure, Verifiables retries with exponential backoff.
POST /v1/monitoring/webhooks
Register a new webhook endpoint.
Authentication: Secret API key
Request body
{
"url": "https://example.com/webhooks/verifiables"
}| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS URL that will receive event payloads |
Response 201
{
"id": "9c1f2a4e-6b3d-4e8f-9a70-2f1c5d8e0b42",
"url": "https://example.com/webhooks/verifiables",
"secret": "3f9a1c...64 hex characters...b7e2",
"active": true,
"createdAt": "2025-06-01T10:00:00.000+00:00"
}The secret is a 64-character hex string. Store it securely: it is only returned at creation time (and on rotation), never in list responses.
curl example
curl -X POST https://api.verifiables.com/v1/monitoring/webhooks \
-H "Authorization: Bearer oat_your_secret_key" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/webhooks/verifiables" }'GET /v1/monitoring/webhooks
List your webhook configurations. The signing secret is never included in list responses.
Authentication: Secret API key
Response 200
[
{
"id": "9c1f2a4e-6b3d-4e8f-9a70-2f1c5d8e0b42",
"url": "https://example.com/webhooks/verifiables",
"active": true,
"createdAt": "2025-06-01T10:00:00.000+00:00",
"updatedAt": "2025-06-01T10:00:00.000+00:00"
}
]PATCH /v1/monitoring/webhooks/:id
Update a webhook's URL or active status.
Authentication: Secret API key
Request body
{
"url": "https://example.com/webhooks/v2",
"active": false
}| Field | Type | Required | Description |
|---|---|---|---|
url | string | No | New endpoint URL |
active | boolean | No | Enable or disable the webhook |
DELETE /v1/monitoring/webhooks/:id
Delete a webhook. Pending deliveries are cancelled.
Authentication: Secret API key
Response 204
No content.
POST /v1/monitoring/webhooks/:id/rotate-secret
Rotate the webhook signing secret. The previous secret is immediately invalidated.
Authentication: Secret API key
Response 200
{
"secret": "8d2e4f...64 hex characters...a1c9"
}Signature verification
Every webhook delivery includes three headers for verification:
| Header | Description |
|---|---|
X-Webhook-Id | Unique delivery identifier |
X-Webhook-Signature | HMAC-SHA256 hex digest of the raw request body |
X-Webhook-Timestamp | ISO 8601 timestamp of the delivery attempt |
User-Agent | Always Verifiables-Monitoring/1.0 |
How to verify
Compute the HMAC-SHA256 of the raw request body using your webhook secret, then compare it to the X-Webhook-Signature header.
import { createHmac, timingSafeEqual } from 'node:crypto'
function verifyWebhookSignature(
secret: string,
rawBody: Buffer,
signatureHeader: string
): boolean {
const expected = createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
return timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signatureHeader, 'hex')
)
}
// Express middleware example
app.post('/webhooks/verifiables', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'] as string
const isValid = verifyWebhookSignature(process.env.WEBHOOK_SECRET!, req.body, signature)
if (!isValid) {
return res.status(401).send('Invalid signature')
}
const payload = JSON.parse(req.body.toString())
console.log('Received event:', payload.event.category, payload.event.siren)
// Process the event...
res.status(200).send('OK')
})Always use timingSafeEqual to prevent timing attacks. Always parse the body only after verifying the signature.
Delivery payload
The webhook body is a JSON envelope with the event nested under event (snake_case fields):
{
"id": "9f30c2de-1a4b-4c5d-8e6f-7a8b9c0d1e2f",
"event_id": "01j1bc8zc7q0axyz4n6p8s0e1x",
"timestamp": "2025-05-29T08:30:00.000+00:00",
"event": {
"siren": "443061841",
"siret": "44306184100047",
"category": "governance_change",
"proceeding": null,
"source": "inpi_rne",
"source_published_at": "2025-05-29T00:00:00.000+00:00",
"effective_date": "2025-05-28T00:00:00.000+00:00",
"after": {
"dirigeants": ["Marie Martin", "Jean Dupont"]
}
}
}| Field | Description |
|---|---|
id | Unique delivery payload identifier (differs per attempt batch) |
event_id | The monitoring event ID (matches id in the events API) |
timestamp | When the payload was built |
event.siren / event.siret | Company identifiers (siret only when establishment-scoped) |
event.category | Event category |
event.proceeding | Proceeding type, only for collective_proceeding |
event.source | inpi_rne, bodacc, or insee_sirene |
event.source_published_at | Publication date in the source registry |
event.effective_date | Legal effective date, when available |
event.after | Snapshot of the changed fields, when available |
Retry schedule
If your endpoint returns a non-2xx status or times out (10 second limit), Verifiables retries with backoff. A delivery is attempted at most 5 times:
| Attempt | Delay after previous failure |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
After the 5th failed attempt, the delivery is marked exhausted. You can replay it manually.
Delivery history
GET /v1/monitoring/webhooks/:id/deliveries
List recent delivery attempts for a webhook.
Authentication: Secret API key
Results are paginated (page, perPage query parameters).
Response 200
{
"meta": { "total": 2, "perPage": 25, "currentPage": 1, "lastPage": 1 },
"data": [
{
"id": "5b7e9d1f-3c2a-4b6d-8e0f-1a2b3c4d5e6f",
"eventId": "01j1bc8zc7q0axyz4n6p8s0e1x",
"webhookConfigId": "9c1f2a4e-6b3d-4e8f-9a70-2f1c5d8e0b42",
"status": "success",
"attempt": 1,
"responseCode": 200,
"responseBody": "OK",
"nextRetryAt": null,
"deliveredAt": "2025-05-29T08:30:05.000+00:00",
"createdAt": "2025-05-29T08:30:04.000+00:00",
"updatedAt": "2025-05-29T08:30:05.000+00:00"
},
{
"id": "6c8f0e2a-4d3b-5c7e-9f10-2b3c4d5e6f70",
"eventId": "01j1bc8zc7q0axyz4n6p8s0e2y",
"webhookConfigId": "9c1f2a4e-6b3d-4e8f-9a70-2f1c5d8e0b42",
"status": "exhausted",
"attempt": 5,
"responseCode": 500,
"responseBody": "Internal Server Error",
"nextRetryAt": null,
"deliveredAt": null,
"createdAt": "2025-05-29T08:30:04.000+00:00",
"updatedAt": "2025-05-29T14:35:04.000+00:00"
}
]
}Delivery status values: pending, success, failed (will retry, see nextRetryAt), exhausted (no more retries).
Replay a delivery
POST /v1/monitoring/webhooks/:id/deliveries/:deliveryId/replay
Re-send a delivery. Useful after fixing a bug in your endpoint or recovering from downtime.
Authentication: Secret API key
Response 200
{
"message": "Delivery queued for replay"
}curl example
curl -X POST https://api.verifiables.com/v1/monitoring/webhooks/9c1f2a4e-6b3d-4e8f-9a70-2f1c5d8e0b42/deliveries/6c8f0e2a-4d3b-5c7e-9f10-2b3c4d5e6f70/replay \
-H "Authorization: Bearer oat_your_secret_key"