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

# Webhooks

> Securely receiving asynchronous updates for company data and monitoring

Topograph uses webhooks to notify you about asynchronous events:

1. **Data Retrieval**: When company data or documents are ready (verification data).
2. **Monitoring**: When a monitored company changes (Monitoring API).

## Configuration

You configure webhooks in the [Topograph Dashboard](https://app.topograph.co) under **Developers > Webhooks**.

## Verification (Security)

You should verify every webhook to ensure it actually came from us.

### 1. Get your signing secret

You can find your endpoint's signing secret (starting with `whsec_`) in the Dashboard.

### 2. Verify the signature

We include headers in every request to allow verification:

* `svix-id`: Unique message ID
* `svix-timestamp`: Timestamp
* `svix-signature`: The signature itself

Use a standard webhook signature verification library or your own HMAC verification code to verify these headers.

```javascript theme={null}
const secret = 'whsec_...';
const headers = req.headers;
const payload = req.body;

const evt = verifyWebhookSignature(payload, headers, secret);
```

## Event Types

### `company.updated`

Sent when company data is retrieved or updated. Webhooks are sent progressively as data becomes available - you may receive multiple webhooks for the same request as different data points complete. The payload matches the response you get by calling `GET /v2/company/{requestId}`, plus an added `type` field.

```json theme={null}
{
  "type": "company.updated",
  "request": {
    "companyId": "932884117",
    "countryCode": "FR",
    "requestId": "86c082a6-c9a0-4243-87d8-7ae18f0ee13a",
    "version": 7,
    "dataStatus": {
      "dataPoints": {
        "company": { "status": "succeeded", "authoritative": true },
        "availableDocuments": { "status": "in_progress" }
      }
    }
  },
  "company": {
    "id": "932884117",
    "countryCode": "FR",
    "legalName": "TOPOGRAPH",
    "status": {
      "localName": "Immatriculée",
      "active": true,
      "statusDetails": {
        "status": "ACTIVE"
      }
    },
    "legalAddress": {
      "addressLine1": "123 Rue Example",
      "city": "Paris",
      "postalCode": "75001",
      "countryCode": "FR"
    }
  },
  "documents": {
    "tradeRegisterExtract": {
      "id": "1c932de4-4610-5506-b48d-4e62529d58e8",
      "name": "Extrait Kbis",
      "format": "pdf",
      "url": "https://..."
    }
  }
}
```

**Key fields:**

| Field                      | Description                                                                                                                                                                                                      |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`                     | Event type identifier (`company.updated`)                                                                                                                                                                        |
| `request.requestId`        | Unique identifier to correlate webhooks with your original request                                                                                                                                               |
| `request.version`          | Monotonic snapshot version for this `requestId`. A higher value is a newer snapshot. Use it to discard stale, out-of-order deliveries (see [Ordering and duplicate delivery](#ordering-and-duplicate-delivery)). |
| `request.metadata`         | The key-value pairs you provided when creating the request, echoed back so you can map the webhook to your internal records                                                                                      |
| `request.dataStatus`       | Status of each data point (`succeeded`, `in_progress`, `enriching`, `failed`). `enriching` means the datapoint is in progress with an intermediate AI enrichment result available.                               |
| `company`                  | Core company information                                                                                                                                                                                         |
| `ultimateBeneficialOwners` | Array of beneficial owners (when available)                                                                                                                                                                      |
| `legalRepresentatives`     | Array of legal representatives (when available)                                                                                                                                                                  |
| `shareholders`             | Array of shareholders (when available)                                                                                                                                                                           |
| `documents`                | Retrieved documents with signed download URLs                                                                                                                                                                    |

### `monitor.notification`

Sent when a monitored company changes status or details.

```json theme={null}
{
  "type": "monitor.notification",
  "monitorId": "clh3k9n0x000008l63vog8wkp",
  "companyId": "932884117",
  "countryCode": "FR",
  "timestamp": "2025-09-26T14:23:45.678Z",
  "changeCategories": ["status", "address"],
  "monitorHasBeenDeactivated": false,
  "metadata": {
    "caseId": "case-12345"
  }
}
```

`metadata` echoes the key-value pairs you set when creating the monitor (`POST /v2/monitors`). Use it to map the notification back to your internal records. The field is only present when the monitor has metadata.

**Change categories:**

* `status` - Company status changed (active, dissolved, etc.)
* `address` - Legal address changed
* `ownership` - Shareholders or UBOs changed
* `financial` - Capital or financial information changed
* `legalRepresentatives` - Legal representatives or directors changed
* `other` - Other changes detected
* `disappeared` - Company no longer found in register

**Testing it:** the portal's sample payload carries no real monitor id and no metadata, so it can't exercise an integration that routes on either. To receive a genuine event on demand, use **Send Test Notification** on a monitor's page in the dashboard, or `POST /v2/monitors/{monitorId}/test-notification`. See [Monitoring](/essentials/monitoring#step-4-test-your-integration).

## Ordering and duplicate delivery

Webhooks for the same `requestId` are **not guaranteed to arrive in order**, and the same event can be delivered more than once due to retries. A `company.updated` that fails on its first delivery is retried later and can land after a newer one that succeeded immediately. Two rules keep your data correct:

1. **Each payload is a full snapshot**, identical to what `GET /v2/company/{requestId}` returned at the time it was sent. Merge it into your store per data point. Do not blindly replace your record based on arrival order, or a late `in_progress` delivery can overwrite newer data.
2. **Use `request.version` to reject stale updates.** It increases every time the snapshot changes for a `requestId`. Track the highest `version` you have applied per `requestId` and ignore any webhook whose `version` is lower than or equal to it. The late `in_progress` delivery carries a lower `version` than the final one, so this drops it.

`request.version` is scoped to a single `requestId`. Do not compare it across different requests.

## Retry Policy

If your server returns an error (non-2xx status) or times out, we will retry delivery with exponential backoff.

* **First retry**: Immediate
* **Subsequent retries**: Increasing delays (seconds, minutes, hours)
* **Duration**: We retry for up to 3 days

Ensure your webhook endpoint is idempotent and responds quickly (return 200 OK immediately, process later).
