> ## Documentation Index
> Fetch the complete documentation index at: https://docs.openregister.de/llms.txt
> Use this file to discover all available pages before exploring further.

# Monitoring

> Monitor companies and people for updates via webhook

## What is Monitoring?

The monitoring API lets you subscribe to changes on specific companies or persons. When the entity is updated in the official register, OpenRegister delivers a webhook notification to your configured endpoint — no polling required.

<Note>
  API monitors are completely separate from the Watchlist feature in the OpenRegister platform. They are independent systems and do not share state. Monitors created via the API only deliver webhooks to your configured endpoint; they do not appear in the platform Watchlist, and entries in your platform Watchlist do not trigger API webhooks.
</Note>

## Getting Started

### Step 1: Configure your webhook URL

Before creating any monitors you must configure a webhook endpoint in your [OpenRegister dashboard](https://openregister.de/keys).

Your webhook endpoint must:

* Accept `POST` requests
* Return a `2xx` HTTP response within 15 seconds
* Be publicly reachable over HTTPS

### Step 2: Create a monitor

Call `POST /v1/monitor` with the entity you want to watch, its type, and the data categories (preferences) you care about. The monitor stays active and delivers notifications until you explicitly delete it.

```mermaid theme={"dark"}
flowchart LR
  A["Create Monitor"] --> B[Entity updated in register]
  B --> C[Webhook delivered to your URL]
  C --> D{Still needed?}
  D -->|Yes| B
  D -->|No| E["Delete Monitor"]
```

## Receiving Webhooks

OpenRegister uses [Svix](https://www.svix.com) to deliver webhook notifications. Svix is a reliable webhook infrastructure service that handles signing, retries, and delivery guarantees on your behalf. For a full overview of consuming Svix-powered webhooks, see the [Svix receiving introduction](https://docs.svix.com/receiving/introduction).

### Delivery behaviour

* OpenRegister sends a `POST` request to your configured endpoint with a JSON body.
* Your endpoint must respond with a `2xx` status code within **15 seconds**. If it does not, Svix treats the delivery as failed and retries automatically with exponential back-off.
* Disable **CSRF protection** for your webhook route — the requests come from Svix servers, not a browser, so CSRF tokens are never present.

### Webhook headers

Every delivery includes three Svix-specific headers used for signature verification:

| Header           | Description                                                                |
| ---------------- | -------------------------------------------------------------------------- |
| `svix-id`        | Unique message ID. Identical across all retry attempts for the same event. |
| `svix-timestamp` | Unix timestamp (seconds) of when the webhook was sent.                     |
| `svix-signature` | One or more base64-encoded HMAC-SHA256 signatures.                         |

## Verifying Webhook Signatures

Verifying the signature on each incoming webhook is important for two reasons:

1. **Authenticity** — confirms the request genuinely came from OpenRegister and not an attacker who found your endpoint URL.
2. **Replay protection** — Svix embeds a timestamp in every signature and the official libraries automatically reject messages with a timestamp more than five minutes old, preventing replay attacks.

For a deeper explanation, see [Why Verify Webhooks](https://docs.svix.com/receiving/verifying-payloads/why) in the Svix docs.

### How to verify

Install the Svix library for your language and call the verify method on the webhook instance. Find your signing secret in your [OpenRegister dashboard](https://openregister.de/keys) under the webhook endpoint you created.

<Warning>
  Always pass the **raw** (unparsed) request body to `verify`. If your framework parses the JSON and you pass a re-serialised string, the cryptographic signature will not match and verification will fail.
</Warning>

<CodeGroup>
  ```typescript TypeScript (Node.js / Next.js) theme={"dark"}
  import { Webhook } from "svix";

  const secret = process.env.WEBHOOK_SECRET!; // whsec_...

  export async function POST(req: Request) {
    const svixId        = req.headers.get("svix-id") ?? "";
    const svixTimestamp = req.headers.get("svix-timestamp") ?? "";
    const svixSignature = req.headers.get("svix-signature") ?? "";

    const body = await req.text(); // raw body — do not parse first

    const wh = new Webhook(secret);
    let event;
    try {
      event = wh.verify(body, {
        "svix-id":        svixId,
        "svix-timestamp": svixTimestamp,
        "svix-signature": svixSignature,
      });
    } catch {
      return new Response("Bad Request", { status: 400 });
    }

    // Process event...
    return new Response("OK", { status: 200 });
  }
  ```

  ```python Python (FastAPI) theme={"dark"}
  from fastapi import Request, Response, status
  from svix.webhooks import Webhook, WebhookVerificationError

  secret = "whsec_..."  # from your dashboard

  async def webhook_handler(request: Request, response: Response):
      headers = request.headers
      payload = await request.body()  # raw bytes — do not parse first

      try:
          wh = Webhook(secret)
          event = wh.verify(payload, headers)
      except WebhookVerificationError:
          response.status_code = status.HTTP_400_BAD_REQUEST
          return

      # Process event...
  ```

  ```go Go theme={"dark"}
  package main

  import (
      "io"
      "net/http"

      svix "github.com/svix/svix-webhooks/go"
  )

  const secret = "whsec_..." // from your dashboard

  func webhookHandler(w http.ResponseWriter, r *http.Request) {
      payload, err := io.ReadAll(r.Body)
      if err != nil {
          w.WriteHeader(http.StatusBadRequest)
          return
      }

      wh, err := svix.NewWebhook(secret)
      if err != nil {
          w.WriteHeader(http.StatusInternalServerError)
          return
      }

      err = wh.Verify(payload, r.Header)
      if err != nil {
          w.WriteHeader(http.StatusBadRequest)
          return
      }

      // Process event...
      w.WriteHeader(http.StatusNoContent)
  }
  ```
</CodeGroup>

For examples in Python (Django/Flask), Ruby, PHP, Rust, and others, see the [full Svix verification guide](https://docs.svix.com/receiving/verifying-payloads/how).

## Testing with Svix Play

[Svix Play](https://play.svix.com) is a free, no-signup webhook debugger. Use it during development to inspect incoming payloads before you have a publicly reachable endpoint.

**To get started:**

1. Go to [play.svix.com](https://play.svix.com) — you'll receive a unique URL instantly.
2. Set that URL as your webhook endpoint in your [OpenRegister dashboard](https://openregister.de/keys).
3. Trigger an event (e.g. create a monitor and wait for an update, or replay a test event). Every delivery appears in the Svix Play UI with full headers, body, and response details.

### Relay to localhost with the Svix CLI

For end-to-end local testing, install the [Svix CLI](https://github.com/svix/svix-cli) and run:

```bash theme={"dark"}
svix listen http://localhost:8080/webhook
```

The CLI prints a public `play.svix.com` URL. Paste it into your dashboard as the endpoint and all deliveries are forwarded to your local server in real time.

### Simulating failures

Append the `force_status_code` query parameter to your Svix Play URL to make it return a specific error code. This lets you verify that OpenRegister's retry logic works end-to-end:

```text theme={"dark"}
https://api.play.svix.com/api/v1/in/{your-token}/?force_status_code=500
```

For the full set of advanced options — echo mode, random failure rate, programmatic history API — see the [Svix Play docs](https://docs.svix.com/play).

## Pricing

| Action           | Cost                                         |
| ---------------- | -------------------------------------------- |
| Create a monitor | 25 credits                                   |
| Active monitor   | Monthly credits per monitor (plan-dependent) |
| List monitors    | 0 credits                                    |
| Delete a monitor | 0 credits                                    |

The monthly credit cost per active monitor depends on your plan. [Contact us](mailto:founders@openregister.de) if you have questions about pricing.

## Update Frequency

How often OpenRegister checks an entity for changes depends on the entity type:

**Companies** — checked for changes at least once per week.

**Persons** — all entities are fully checked at least once per month. When a person incorporates a new company, that change is detected daily.

Need faster updates? [Contact us](mailto:founders@openregister.de) to discuss custom frequencies.

## Preferences

Preferences control which data categories trigger a notification. You must specify at least one preference when creating a monitor, and all preferences must match the `entity_type`.

### Company preferences (`entity_type: company`)

| Preference       | What triggers a notification                                                     |
| ---------------- | -------------------------------------------------------------------------------- |
| `basic`          | Core firmographic data — name, registered address, legal form, or company status |
| `representation` | Directors, officers, managing partners, or authorised signatories                |
| `financials`     | Annual accounts or financial statements filed                                    |
| `documents`      | New documents or publications filed with the register                            |
| `ownership`      | Direct owners (shareholders) of the company                                      |
| `holdings`       | Companies in which this company holds a stake                                    |

### Person preferences (`entity_type: person`)

| Preference             | What triggers a notification                                  |
| ---------------------- | ------------------------------------------------------------- |
| `management_positions` | Board or management roles the person holds across any company |
| `holdings`             | Companies in which this person holds a stake                  |

<Warning>
  Passing a preference that does not apply to the given `entity_type` returns a `400 Bad Request` validation error. For example, you cannot use `representation` when monitoring a person.
</Warning>

## Use Cases

**Compliance & KYC refresh** — Monitor the companies and persons in your portfolio. When a director change, ownership update, or new financial filing is detected you receive an immediate notification, letting you trigger a re-verification workflow without scheduling periodic re-checks.

**Portfolio monitoring** — Subscribe to `basic` and `representation` changes across all portfolio companies to track legal form conversions, address moves, insolvency filings, or management changes as they appear in the register.

**Ownership chain alerting** — Combine `ownership` and `holdings` preferences to detect restructuring events in complex ownership chains. Useful for banks, investors, and compliance teams that need to know when a beneficial ownership chain changes.

**Due diligence pipelines** — Automatically re-fetch company data and refresh documents when you receive a `documents` or `financials` notification, ensuring your internal records stay in sync with the official register.

## Managing Monitors

Use the three monitor endpoints together to manage your subscriptions:

| Action            | Endpoint                                                     |
| ----------------- | ------------------------------------------------------------ |
| Create a monitor  | [`POST /v1/monitor`](/endpoint/monitor-create)               |
| List all monitors | [`GET /v1/monitor`](/endpoint/monitor-list)                  |
| Delete a monitor  | [`DELETE /v1/monitor/{entity_id}`](/endpoint/monitor-delete) |

### The `disabled` flag

Each monitor item in the list response includes a `disabled` boolean. A monitor is disabled when your account is downgraded to a plan that no longer includes monitoring access. Disabled monitors are preserved in your list so you do not lose your configuration, but they stop delivering notifications.

Monitors are **not** automatically re-enabled on upgrade — this is intentional to prevent unexpected billing. To re-enable a disabled monitor, contact [founders@openregister.de](mailto:founders@openregister.de).
