Webhooks
What a monitoring event looks like when it reaches your endpoint, and how to handle it safely.
A monitor is only half of monitoring. The other half is the endpoint we deliver to. This page is the contract: what arrives, what verifies it, and what you must not assume.
Configure an endpoint
Webhooks are configured in API management under Webhooks, not through this API. You give us an HTTPS URL and choose which event types it should receive; leaving the selection empty sends every type.
Each endpoint gets its own signing secret. Keep it — you need it to verify deliveries, and it is the only thing separating a real event from anyone who learns your URL.
What arrives
Deliveries still carry the old shape
The contract publishes the v1 payload below, and the reference documents it. What is delivered today is still the previous shape, because the service that produces these events has not been moved onto the v1 code path yet.
Write your handler against company_id and event_type, which exist in both, and
re-read the company rather than trusting any other field. The two shapes agree on
everything a correct handler needs.
Delivered today
{
"event_type": "ADDRESS_CHANGE",
"company_name": "Beispiel GmbH",
"company_id": "c359aa06-1ac0-4dab-9c60-d5c69a06ff2c",
"event_date": "2026-09-15",
"event_data": {
"field": "full_address",
"old": "Hauptstraße 1, 10115 Berlin",
"new": "Friedrichstraße 200, 10117 Berlin",
"status": null,
"content": null
},
"created_at": "2026-09-15T18:42:11.930000"
}| Field | Notes |
|---|---|
event_type | The old uppercase vocabulary — ADDRESS_CHANGE, REPRESENTATIVE_ADDED and so on. |
company_name | The name at the time the event was raised, not necessarily the current one. |
company_id | The id to re-read. This is the field you actually act on. |
event_date | When the change was dated in the register. |
event_data.field | Which field changed. null for event types that are not field-level. |
event_data.old / .new | The values, where we have both. old is null on an addition. |
created_at | When we raised the event. Not when the change happened — see detection latency. |
event_data is always present, but every field inside it may be null. Read it
defensively rather than destructuring.
What it becomes
{
"id": "6ba7b810-9dad-41d1-80b4-00c04fd430c8",
"object": "event",
"type": "company.address.changed",
"created_at": "2026-09-15T18:42:11Z",
"effective_date": "2026-09-15",
"monitor_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"company": { "object": "company", "id": "c359aa06-1ac0-4dab-9c60-d5c69a06ff2c", "name": "Beispiel GmbH" },
"change": { "field": "address", "previous": "Hauptstraße 1, 10115 Berlin", "current": "Friedrichstraße 200, 10117 Berlin" },
"officer": null
}Three things change that are worth planning for: type uses the same dotted vocabulary as
monitors, the body carries its own id so you no
longer have to deduplicate on the svix-id header, and monitor_id tells you which of your
monitors fired.
Verifying a delivery
Delivery is handled by Svix, which implements the Standard Webhooks specification. Three headers come with every request:
| Header | Meaning |
|---|---|
svix-id | The unique message identifier. |
svix-timestamp | Delivery timestamp, used to reject replays. |
svix-signature | HMAC signature over the id, timestamp and body. |
Verify with the official library rather than by hand — it does constant-time comparison and the timestamp tolerance for you:
import { Webhook } from 'svix';
const wh = new Webhook(process.env.REGISTERCHECK_WEBHOOK_SECRET!);
app.post('/hooks/registercheck', express.raw({ type: 'application/json' }), (req, res) => {
let event;
try {
event = wh.verify(req.body, { // the RAW body, not the parsed object
'svix-id': req.headers['svix-id'] as string,
'svix-timestamp': req.headers['svix-timestamp'] as string,
'svix-signature': req.headers['svix-signature'] as string,
});
} catch {
return res.status(400).end(); // unverified: do not process
}
res.status(200).end(); // acknowledge, then work
void handle(req.headers['svix-id'] as string, event);
});Signature verification needs the raw request body. A JSON body-parser mounted earlier will re-serialise it and the signature will not match.
Deduplicate on svix-id, not on the body
There is no event_id in the request body. The identifier to deduplicate on is the
svix-id header.
We do send an event id to Svix, but it is a sender-side idempotency key — it stops us
from raising the same event twice and never appears in what you receive. Reading
req.body.event_id gives you undefined, and a deduplication check against undefined
silently passes for every delivery.
Assume at-least-once delivery: a slow or failing endpoint is retried, and a retry carries
the same svix-id.
Treat the payload as a signal
event_data.new tells you what we saw at detection time. It is not a substitute for the
company record, and it is not guaranteed to still be current when you process it. Re-read
the company and act on that:
event arrives → verify → dedupe on svix-id → GET /companies/{company_id} → applyThe track changes guide walks through this end to end.