Docs
Monitoring

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

  1. Register a webhook URL. You receive a secret for signature verification.
  2. Events fire and Verifiables delivers them to your endpoint.
  3. Verify the HMAC signature on each delivery.
  4. Respond with a 2xx status within 10 seconds.
  5. 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"
}
FieldTypeRequiredDescription
urlstringYesHTTPS 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
}
FieldTypeRequiredDescription
urlstringNoNew endpoint URL
activebooleanNoEnable 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:

HeaderDescription
X-Webhook-IdUnique delivery identifier
X-Webhook-SignatureHMAC-SHA256 hex digest of the raw request body
X-Webhook-TimestampISO 8601 timestamp of the delivery attempt
User-AgentAlways 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"]
    }
  }
}
FieldDescription
idUnique delivery payload identifier (differs per attempt batch)
event_idThe monitoring event ID (matches id in the events API)
timestampWhen the payload was built
event.siren / event.siretCompany identifiers (siret only when establishment-scoped)
event.categoryEvent category
event.proceedingProceeding type, only for collective_proceeding
event.sourceinpi_rne, bodacc, or insee_sirene
event.source_published_atPublication date in the source registry
event.effective_dateLegal effective date, when available
event.afterSnapshot 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:

AttemptDelay after previous failure
1Immediate
21 minute
35 minutes
430 minutes
52 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"