# Webhooks

> Modtag ændringer i de virksomheder, du overvåger, som signerede HTTP-kald til dit eget system.

Når du [overvåger en virksomhed](https://cvrradar.dk/docs/endpoints/overvaag.md) med en `webhook_url`, sender cvrradar et `POST` med JSON, hver gang der er registreret nye ændringer.

## Hændelsen

```http
POST /cvrradar HTTP/1.1
Content-Type: application/json
User-Agent: cvrradar-webhook/1
X-Cvrradar-Event: virksomhed.aendret
X-Cvrradar-Signature: t=1763024400,v1=5f2b…
```

```json
{
  "id": "0d7e7c1e-6f7b-4a39-9c6e-2b1f0a8d4e11",
  "type": "virksomhed.aendret",
  "oprettet": "2026-11-13T03:20:00.000Z",
  "data": {
    "cvr": "10203040",
    "aendringer": [
      {
        "id": 4,
        "tid": "2026-11-13T03:00:00Z",
        "dato": "2026-11-12",
        "type": "adresse",
        "tekst": "Adresse ændret til Aarhus C",
        "fra": null,
        "til": { "by": "Aarhus C" }
      }
    ]
  },
  "kilde": "cvrradar.dk, ud fra Erhvervsstyrelsens CVR"
}
```

`aendringer` har samme form som [Historik](https://cvrradar.dk/docs/endpoints/historik.md). Brug `id` til at undgå dubletter: den samme hændelse kan komme mere end én gang, hvis dit svar gik tabt.

## Tjek signaturen

Headeren `X-Cvrradar-Signature` har formen `t=<unix-tid>,v1=<hex>`, hvor `<hex>` er HMAC-SHA256 af teksten `"<t>.<rå body>"` med din webhook-hemmelighed. Afvis kaldet, hvis signaturen ikke passer, eller hvis `t` er mere end 5 minutter gammel.

```js JavaScript
import { createHmac, timingSafeEqual } from 'node:crypto';

export function gyldig(raaBody, header, hemmelighed) {
  const dele = Object.fromEntries(header.split(',').map((d) => d.split('=')));
  const t = Number(dele.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const forventet = createHmac('sha256', hemmelighed).update(`${t}.${raaBody}`).digest('hex');
  const a = Buffer.from(forventet);
  const b = Buffer.from(dele.v1 ?? '');
  return a.length === b.length && timingSafeEqual(a, b);
}
```
```php PHP
function gyldig(string $raaBody, string $header, string $hemmelighed): bool {
    parse_str(str_replace(',', '&', $header), $dele);
    $t = (int) ($dele['t'] ?? 0);
    if (!$t || abs(time() - $t) > 300) return false;
    $forventet = hash_hmac('sha256', $t . '.' . $raaBody, $hemmelighed);
    return hash_equals($forventet, $dele['v1'] ?? '');
}
```
```python Python
import hashlib, hmac, time

def gyldig(raa_body: bytes, header: str, hemmelighed: str) -> bool:
    dele = dict(d.split("=", 1) for d in header.split(","))
    t = int(dele.get("t", 0))
    if not t or abs(time.time() - t) > 300:
        return False
    forventet = hmac.new(hemmelighed.encode(), f"{t}.".encode() + raa_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(forventet, dele.get("v1", ""))
```

Brug altid den rå body, som den kom, ikke JSON, du har læst og skrevet igen.

## Svar og genforsøg

- Svar med en `2xx`-status inden for 10 sekunder. Lav det tunge arbejde bagefter, fx i en kø.
- Svarer du ikke med `2xx`, prøver cvrradar igen tre gange med stigende pause. Derefter opgives leveringen, og ændringen kommer med i næste levering eller i den daglige mail, hvis du har slået mail til.
- Adressen skal være HTTPS og må ikke pege på et privat netværk.
