Track changes
Replace a polling loop with a monitor and a webhook.
The shape of it
monitor once → we re-read the register → event fires → your webhook → you re-read the companyYou monitor a company. We detect that its register entry moved. Your destination gets told. You fetch the new state.
A monitor follows the companies you name. To follow changes across every company -- all new managing directors in Bavaria, every insolvency -- poll the register-event feed instead: Follow register changes.
Monitor a company
import uuid
def watch(court: str, register_type: str, number: str,
events=("*",), crm_id: str | None = None) -> str:
r = session.post(
f"{BASE}/monitors",
headers={"Idempotency-Key": str(uuid.uuid4())}, # reuse it if you retry this call
json={
"register": {"court": court, "type": register_type, "number": number},
"event_types": list(events),
"metadata": {"crm_id": crm_id} if crm_id else {},
},
)
if r.status_code == 409 and r.json()["code"] == "conflict":
return r.json()["existing_id"] # already watching it
r.raise_for_status()
return r.json()["id"]
watch("München", "HRB", "999999", events=("representation", "capital"), crm_id="0015g00000XyZ")No search first: the register identity names the company. No list-then-check either: a second
monitor on the same company answers 409 with the existing monitor's id, and the
Idempotency-Key makes a retry after a timeout safe — creating a monitor costs 10 credits.
Narrow the events
* is the default and it is noisy. Monitor what you will act on:
| You care about | Monitor |
|---|---|
| A counterparty's solvency | insolvency, dissolution, capital |
| Who can sign | representation |
| Change of control | shareholders — accepted, but not delivered yet: the monitor lists these in unavailable_event_types |
| Keeping records tidy | identity, registered_office, conversion |
"Who can sign" is one set of types: an authorised signatory holds a role here, so a grant of
Prokura and a board appointment both raise company.role.added, and data.role_type says
which.
One list names every event type (GET /event-types);
monitorable says which a monitor can ask for. A register entry keeps its name in the history and
on a monitor, and four types are monitoring's own. Shareholders are reported one by
one (company.shareholder.added, company.shareholding.changed, company.shareholder.removed), where
the history records a newly filed shareholder list once (company.shareholder_list.changed). And company.financial_statement.published
comes from the Bundesanzeiger and has no counterpart in the register history.
Change the selection later with
PATCH /monitors/{id}, which touches only
what you send — so {"status": "paused"} stops delivery without losing your event types.
Handle the webhook
app.post('/hooks/registercheck', async (req, res) => {
const delivery = verify(req); // see Webhooks: raw body + the three svix-* headers
res.status(200).end(); // acknowledge first, work after
if (await alreadyProcessed(delivery.id)) return; // delivery.id is the svix-id; a retry repeats it
for (const event of delivery.events) { // one for an instant destination, a period's for a digest
const fresh = await fetchCompany(event.subject.id); // the payload is a signal, not state
await applyChange(event.subject.id, event.type, fresh);
}
await markProcessed(delivery.id);
});Three rules, in order of how much trouble they save you:
- Acknowledge immediately. A slow handler is a retried handler.
- Deduplicate on the delivery
id(also thesvix-idheader). Assume at-least-once delivery; a retry repeats it. See Webhooks. - Re-read the company. Treat the event as "something changed", not as the new value.
What this is not
Detection runs on a cycle against the register, and the register itself lags the real world by days or weeks. Expect to hear about a change a day or two after it is published, and do not build anything that needs to know within the hour.
Housekeeping
Only creating a monitor costs anything; listing, reading, updating and deleting are free.
So read GET /monitors whenever you want the truth rather than trusting a local index, and
delete what you no longer watch — the cost of a monitor is the 10 credits you already paid,
not an ongoing charge.