Monitoring & Webhooks
Watch a company for change and receive a webhook when something happens
Overview
A monitor watches one company and records an event every time something
about it changes. Each event carries a specific eventCode — INSOLVENCY_OPENED,
DIRECTOR_ADDED, COMPANY_DISSOLVED — and a broader category such as status,
ownership, or disappeared. Set a webhookUrl and every event is delivered to
your endpoint as it happens; with or without one, every event stays queryable
through the API.
Event codes mean the same thing in every jurisdiction.
To test a webhook receiver without a real company, use the Sandbox: its monitoring sources fire on every check.
Create a monitor
1. Find the sources for the company
A monitor watches a company through one or more sources. The sources available depend on the company's jurisdiction:
curl "https://api.kausate.com/v2/monitors/sources?kausateId=co_de_UJybUvCx7CrPyqCtsDXsS" \
-H "X-API-Key: your_api_key"{
"sources": [
{
"name": "company_report",
"dataSource": "de-handelsregister",
"integrationName": "Handelsregister",
"jurisdictions": null,
"mode": "per_company_pull",
"producesCategories": [
"address",
"disappeared",
"financial",
"legalRepresentatives",
"other",
"status"
],
"requiredCapability": "company_report"
},
{
"name": "de_insolvenzbekanntmachungen.feed",
"dataSource": "de-insolvenzbekanntmachungen",
"integrationName": "Insolvenzbekanntmachungen",
"jurisdictions": ["de"],
"mode": "global_feed",
"producesCategories": ["financial", "other", "status"],
"requiredCapability": null
}
]
}producesCategories tells you what kinds of event each source can emit. Pass
name verbatim when you create the monitor.
2. Create the monitor
curl -X POST https://api.kausate.com/v2/monitors \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"kausateId": "co_de_UJybUvCx7CrPyqCtsDXsS",
"sources": ["company_report", "de_insolvenzbekanntmachungen.feed"],
"scheduleCron": "0 9 * * *",
"webhookUrl": "https://your-server.com/webhooks/kausate",
"categoriesFilter": ["status", "ownership", "disappeared"],
"metadata": { "caseId": "CASE-12345", "team": "compliance" }
}'| Field | Meaning |
|---|---|
kausateId | The company to watch |
sources | Source names, exactly as returned by discovery |
scheduleCron | How often scheduled sources check. Optional, defaults to monthly |
webhookUrl | Where events are delivered. Optional |
categoriesFilter | Deliver only these categories. Optional, defaults to all |
eventCodesFilter | Deliver only these event codes. Optional, defaults to all |
autoDeactivateCategories | Categories that switch the monitor off. Defaults to ["disappeared"] |
metadata | Your own JSON object, echoed back on every event. Optional, 4 KB max |
See Create Monitor for the full schema.
3. Receive events
Each detected change is POSTed to webhookUrl:
{
"event": "monitor.change_detected",
"monitor_id": "2859d110-ea00-4c49-8547-f359672d20f3",
"kausate_id": "co_de_UJybUvCx7CrPyqCtsDXsS",
"event_id": "61197641-56f6-4c10-90cb-716aca5a5752",
"event_code": "COMPANY_STATUS_CHANGED",
"category": "status",
"severity": "info",
"detected_at": "2026-08-06T06:52:41.397800+00:00",
"diff_path": "companyReport.basicInformation.status.normalized",
"before": "active",
"after": "inactive",
"metadata": {
"caseId": "CASE-12345",
"team": "compliance",
"source": "company_report",
"api_url": "https://api.kausate.com/v2/monitors/2859d110-ea00-4c49-8547-f359672d20f3/events/61197641-56f6-4c10-90cb-716aca5a5752"
}
}before and after hold the values that changed, and diff_path points at the
field inside the company report — for example
companyReport.basicInformation.status.normalized. A check records at most one
event per event code, so when several people or shareholdings change at once,
re-read the company report or shareholder graph for the full picture.
For a feed event the publication itself is the change: before is null,
diff_path is null, and after carries the full publication.
Sources
Scheduled sources (mode: "per_company_pull") re-read the company on the
cron you set and report what moved since last time. company_report covers
status, addresses, directors, share capital, name, and legal form.
shareholder_graph covers ownership. Both are available wherever the
jurisdiction supports the underlying operation.
The first check records a baseline. Events start from the second check onward.
Publication feeds (mode: "global_feed") track an official publication stream
— insolvency notices, for instance — and raise an event when a publication
naming your company appears. Feeds are tied to one jurisdiction and are checked
once a day; scheduleCron does not apply to them.
New sources are added over time. Read the list at runtime rather than
hardcoding it: GET /v2/events/sources returns
every source, GET /v2/monitors/sources
the ones for a given company.
Categories and event codes
Every event belongs to exactly one of seven categories:
| Category | Covers |
|---|---|
status | Company status changes (active → liquidating, insolvency proceedings) |
address | Registered or business address changes |
ownership | Shareholder or UBO additions, removals, percentage changes |
financial | Share-capital changes, financial publications |
legalRepresentatives | Director and authorized-signatory changes |
other | Name, legal form, activity changes |
disappeared | Company dissolved, struck off, liquidated, or no longer found |
Route on category for broad handling, or on eventCode for one specific
transition such as INSOLVENCY_OPENED.
match payload["category"]:
case "disappeared":
ops_alert(payload)
case "ownership":
compliance_review(payload)
case "status" if payload["event_code"].startswith("INSOLVENCY_"):
risk_team(payload)
case _:
archive(payload)The full set of codes:
| Event code | Category | Meaning |
|---|---|---|
NAME_CHANGED | other | Legal name changed |
LEGAL_FORM_CHANGED | other | Legal form changed (GmbH → AG, Ltd → PLC) |
ACTIVITIES_CHANGED | other | Declared business activities changed |
REGISTERED_ADDRESS_CHANGED | address | Registered office address changed |
BUSINESS_ADDRESS_CHANGED | address | Operating address changed |
JURISDICTION_CHANGED | address | Registry jurisdiction changed (cross-border seat transfer) |
COMPANY_STATUS_CHANGED | status | Status on the register changed |
REGISTRATION_DATE_CHANGED | status | A date on the register changed, including its last-changed date |
VAT_REGISTRATION_CHANGED | status | VAT registration granted or cancelled |
FISCAL_INACTIVITY_CHANGED | status | Fiscal-inactivity flag set or cleared |
SHARE_CAPITAL_CHANGED | financial | Share-capital amount changed |
COMPANY_DISSOLVED | disappeared | Company dissolved |
COMPANY_STRUCK_OFF | disappeared | Struck off the register |
COMPANY_LIQUIDATED | disappeared | Company entered liquidation or strike-off |
COMPANY_NOT_FOUND | disappeared | Register no longer returns the company |
DIRECTOR_ADDED | legalRepresentatives | Director or officer appointed |
DIRECTOR_REMOVED | legalRepresentatives | Director or officer left |
DIRECTOR_ROLE_CHANGED | legalRepresentatives | An existing director's role changed |
AUTHORIZED_SIGNATORY_CHANGED | legalRepresentatives | Signatory powers changed |
UBO_ADDED | ownership | New UBO declared |
UBO_REMOVED | ownership | UBO removed |
UBO_PERCENTAGE_CHANGED | ownership | An existing UBO's stake changed |
SHAREHOLDER_ADDED | ownership | New shareholder |
SHAREHOLDER_REMOVED | ownership | Shareholder left |
SHAREHOLDING_PERCENTAGE_CHANGED | ownership | An existing shareholder's stake changed |
INSOLVENCY_PROTECTIVE_MEASURES | status | Protective measures ordered |
INSOLVENCY_DISMISSED_FOR_LACK_OF_ASSETS | status | Application dismissed for lack of assets |
INSOLVENCY_OPENED | status | Insolvency proceedings formally opened |
INSOLVENCY_DECISION | status | Decision issued in ongoing proceedings |
INSOLVENCY_LIFTED | status | Proceedings lifted or closed |
INSOLVENCY_DISTRIBUTION_PUBLISHED | financial | Distribution to creditors published |
INSOLVENCY_DISCHARGE_DECISION | status | Discharge decision issued |
INSOLVENCY_PLAN_SUPERVISED | status | Insolvency plan or supervised proceedings |
INSOLVENCY_OTHER | other | Insolvency event not covered above |
FILING_PENALTY_IMPOSED | other | Penalty for a late or missing filing published |
LIQUIDATION_CREDITOR_CALL_PUBLISHED | status | Call to creditors published in a liquidation |
SHAREHOLDER_MEETING_CONVENED | other | Shareholder meeting convened |
FINANCIAL_STATEMENT_PUBLISHED | financial | Financial statement published |
OTHER | other | Change without a more specific code |
A few codes are reserved and no source emits them yet:
UBO_ADDED, UBO_REMOVED, UBO_PERCENTAGE_CHANGED, BUSINESS_ADDRESS_CHANGED,
JURISDICTION_CHANGED, VAT_REGISTRATION_CHANGED, FISCAL_INACTIVITY_CHANGED,
and COMPANY_NOT_FOUND. Ownership changes reach you today
as SHAREHOLDER_ADDED, SHAREHOLDER_REMOVED, and
SHAREHOLDING_PERCENTAGE_CHANGED. Handle the reserved codes if they arrive, but
do not build a feature that depends on them.
GET /v2/events/taxonomy returns
this table, including codes added later.
Filtering deliveries
categoriesFilter and eventCodesFilter decide what reaches your webhook. Set
both and an event must satisfy each independently to be delivered.
Filters do not backfill. A feed event that a filter excludes is never recorded and cannot be recovered. Events from scheduled sources are always recorded and stay queryable even when a filter kept them off your webhook. If you might want a category later, subscribe to it now and discard it on your side.
Turning a monitor off automatically
By default, any delivered event in the disappeared category switches the
monitor off and records a deactivationReason. That includes
COMPANY_LIQUIDATED, which fires when liquidation or strike-off starts. A filter
that excludes disappeared events also prevents the switch-off. Events already
collected stay queryable.
Change the behaviour when you create the monitor:
// also switch off when insolvency proceedings open
"autoDeactivateCategories": ["disappeared", "status"]
// never switch off (an empty list is different from omitting the field)
"autoDeactivateCategories": []A switched-off monitor stops checking but can still be read, and its events
listed and replayed. Use
DELETE /v2/monitors/{id} to remove it
entirely.
Correlating events with your own records
The optional metadata object you set on a monitor is echoed back on every event
it produces:
"metadata": { "caseId": "CASE-12345", "team": "compliance" }It comes back verbatim on monitor responses, and merged into each event's
metadata alongside Kausate's own annotations — source on every event,
external_id and publication details on feed events, and api_url on webhook
deliveries. Kausate's keys win on a name collision, so avoid source,
external_id, jurisdiction, api_url and any key starting with native_ if
you need your own values back unchanged. The object must be JSON and 4 KB or smaller.
Webhook delivery
Monitor events are POSTed to the webhookUrl on the monitor with two headers,
Content-Type: application/json and Kausate-Version.
Delivery is at least once. A failed delivery is retried with exponential
backoff, starting at one second and settling at one attempt every four hours, for
up to two days. The same event can arrive twice, so deduplicate on event_id,
which is stable across retries and replays. Return a 2xx within 30 seconds;
anything else counts as a failure and is retried.
If your endpoint is down for longer than two days, the delivery is abandoned. The event itself is not lost — replay it once you are back up.
A monitor cannot be edited. To change webhookUrl, delete the monitor and
create a new one.
Each monitor carries its own webhookUrl. To route everything to one endpoint,
set the same URL on every monitor.
Authenticating the requests
Monitor webhooks are not signed. Unlike the order-result webhooks
described in the Webhooks reference, there is no
HMAC signature on a monitor.change_detected payload, so your endpoint has to
authenticate the request another way.
- Recommended — host the receiver on an unguessable HTTPS path, such as
https://your-app.example.com/webhooks/kausate-monitors/<random-token>, and reject every other path. Treat the path as a shared secret. - Stronger — put a reverse proxy in front of the receiver that injects a
static
Authorizationheader for requests on the monitor-webhook path, and check it in your handler. Monitor webhooks do not carry custom headers, so this has to live in your infrastructure.
You can additionally restrict your endpoint to Kausate's outbound IP addresses, as a second layer rather than your only control.
Event history and replay
Every event a monitor records is queryable, including ones a filter kept off your webhook.
# Filter by eventCode, category, since, and until
curl "https://api.kausate.com/v2/monitors/$MONITOR_ID/events?limit=100&category=status&since=2026-05-01T00:00:00Z" \
-H "X-API-Key: your_api_key"Each event includes a deliveries array with the status, target,
deliveredAt and lastError of each delivery.
Re-send a single event with
POST /v2/monitors/{id}/events/{eventId}/replay,
which returns 202 {"status":"queued"}:
curl -X POST "https://api.kausate.com/v2/monitors/$MONITOR_ID/events/$EVENT_ID/replay" \
-H "X-API-Key: your_api_key"A replay reuses the original event_id. There is no bulk replay; re-send many
events one at a time.
Choosing a schedule
scheduleCron controls how often scheduled sources re-read the company. Every
scheduled source makes its own fresh registry call on each run, so the cron and
the number of scheduled sources together decide how much work a monitor does.
- Omit
scheduleCronfor the monthly default (0 0 1 * *), or pass any valid five-field expression —0 9 * * 1for Monday mornings,0 9 * * *for daily. - A monitor watching both
company_reportandshareholder_graphmakes two registry calls per run, not one. Subscribe only to the sources you act on. - Publication feeds ignore the cron. They are checked once a day, so expect an event within about a day of publication. A monitor with only feed sources never runs a scheduled check.
- Leave auto-deactivation on so dissolved companies stop being checked.
Troubleshooting
My webhook is not firing
- Is the monitor still on? Read
GET /v2/monitors/{id}and checkisActive. If auto-deactivation switched it off,deactivationReasonsays why. - Was the event recorded? List the monitor's events with
sinceset to your last known delivery. If the event is there, it was detected, and the problem is filtering or delivery. - Do your filters allow it? The same response returns
categoriesFilterandeventCodesFilter. An event must satisfy every filter that is set. - Is your endpoint healthy? Replay the event and watch your server logs for the new attempt. Any non-2xx response, or one taking longer than 30 seconds, counts as a failure.
- Still failing after a replay? The problem is on your side — DNS, TLS,
firewall, or a slow handler. The
deliveriesarray on the event carrieslastErrorfrom the last attempt.
I am receiving the same event twice
Expected — delivery is at least once. Deduplicate on event_id.
An address change fired but the address looks the same
Addresses and legal representatives are each compared as a whole list, so a
register that returns the same entries in a different order can raise
REGISTERED_ADDRESS_CHANGED or DIRECTOR_ROLE_CHANGED without a real change.
Treat those events as a prompt to re-read the company report.
A feed event matched the wrong company
Feeds match publications to companies on the identifiers the publication carries.
Two companies sharing an identifier across different sub-registers, or a
publication still using an identifier from before a transfer, can produce a false
match. Check metadata.external_id and the publication in after, and contact
support if the mismatch is systematic.
before is null
Expected for feed events, where the publication itself is the change, and when an entry was added or the field was previously absent.
My monitor switched itself off and I want it back
Recreate it with autoDeactivateCategories: [], or contact support to reactivate
the existing one. Recreating is usually faster.
Common errors
Branch on the code field of the error body — never on the wording of
detail. See the Errors guide for the body shape and the
prefix-matching contract.
| Status | code | Reason | Fix |
|---|---|---|---|
404 | resource.not_found | The kausateId, monitor or event doesn't exist | Search for the company with /v2/companies/search, or re-list your monitors |
422 | request.invalid | The request body is well-formed but unusable: unknown monitoring source, a source not applicable to that company, an invalid cron, or a stale eventCodesFilter value | Read detail for which one; discover sources via /v2/events/sources or /v2/monitors/sources?kausateId=…, codes via /v2/events/taxonomy |
422 covers every request-shape failure, so code alone does not distinguish
the four causes — detail names the offending field. Everything above is
client-fixable; nothing here should be retried unchanged.
Last updated on