Webhooks: få besked, når adresser ændrer sig

Log ind for at få en JWT, opret, vis og slet webhooks via /api/v1/webhooks, og gem den hemmelighed, der kun vises én gang.

4 min Niveau: Avanceret ✓ Kode testet

En webhook sender et signeret POST til din server, når de entiteter, du følger, har ændret sig. Så slipper du for selv at spørge efter nye hændelser hele tiden. Webhooken fortæller, hvad der er ændret. Selve data henter du med hændelses-API'et, hvis du har brug for mere end id'erne.

Webhooks administreres kun via API'et. Der er ingen side til dem i dashboardet endnu, så alle trin herunder er HTTP-kald.

1. Forbered dit endpoint

Før du opretter webhooken, skal du have en URL, der:

  • bruger https://. Andre URL'er afvises med 400 url_must_be_https.
  • kan nås fra internettet. Leveringerne kommer fra vores server.
  • svarer med en 2xx-status inden for 6 sekunder. Gør selve arbejdet i en kø bagefter.
  • svarer direkte uden redirect. En 3xx-status tæller som leveret, men indholdet når aldrig frem til den side, der redirectes til.
  • tjekker signaturen. Se Verificér webhook-signaturen.

2. Log ind og hent en JWT

Webhook-endpoints bruger din kontos login, ikke din API-nøgle. Log ind med e-mail og adgangskode:

Shell#login
curl -s https://api.danadresse.dk/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "dig@firma.dk", "password": "din-adgangskode"}'
JSON#login-response
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
  "token_type": "Bearer",
  "user": {"id": "…", "email": "dig@firma.dk", "plan": "free", "…": "…"}
}

Tokenet er gyldigt i 24 timer. Oprettede du kontoen med Google, har den ingen adgangskode. Sæt en via Glemt adgangskode først. Forkert login giver 401 invalid_credentials, og for mange forsøg giver 429 too_many_attempts.

3. Opret webhooken

Send URL'en og de entiteter, du vil følge. Gyldige entiteter er postnummer, kommune, navngivenvej, vejstykke, adgangsadresse og adresse. kommune kan vælges, men der kommer i øjeblikket ingen løbende ændringer for kommuner:

Shell#create
curl -s https://api.danadresse.dk/api/v1/webhooks \
  -H "Authorization: Bearer $DANADRESSE_JWT" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://app.firma.dk/hooks/danadresse", "entities": ["adresse", "adgangsadresse"]}'
JSON#create-response
{
  "id": "6f1c2a9e-1d2b-4c51-9a57-2f4f0d3e8b11",
  "url": "https://app.firma.dk/hooks/danadresse",
  "entities": ["adresse", "adgangsadresse"],
  "is_active": true,
  "failure_count": 0,
  "created_at": "2026-10-15T12:20:04",
  "last_delivery_at": null,
  "secret": "9c4e…64 hex-tegn…",
  "warning": "Save the secret now — it is shown only once."
}

secret vises kun i dette svar. Gem den i din secrets manager eller i en miljøvariabel på serveren, der modtager webhooken. Mister du den, skal du slette webhooken og oprette en ny.

En tom liste eller en ukendt entitet giver 400 invalid_entities, og details.invalid viser, hvilke navne der var forkerte.

4. Vis og slet

Shell#list-delete
# Vis dine webhooks (uden hemmeligheden)
curl -s https://api.danadresse.dk/api/v1/webhooks \
  -H "Authorization: Bearer $DANADRESSE_JWT"

# Slet en
curl -s -X DELETE https://api.danadresse.dk/api/v1/webhooks/6f1c2a9e-1d2b-4c51-9a57-2f4f0d3e8b11 \
  -H "Authorization: Bearer $DANADRESSE_JWT"

Listen viser is_active, failure_count og last_delivery_at for hver webhook. Hold øje med dem, se Webhooks: idempotens og fejl.

Der er ikke et endpoint til at ændre en webhook. Vil du skifte URL eller entiteter, eller genaktivere en webhook, der er slået fra efter for mange fejl, så slet den og opret en ny. Den nye får en ny hemmelighed.

5. Gør det fra et script

Det samme i Python, så du kan køre det fra din deploy-pipeline. E-mail og adgangskode læses fra miljøvariabler:

Python#admin-script
"""webhook_admin.py: opret, vis og slet webhooks via /api/v1/webhooks.

    DANADRESSE_EMAIL=… DANADRESSE_PASSWORD=… python webhook_admin.py list
    … python webhook_admin.py create https://app.example.dk/hooks/danadresse adresse adgangsadresse
    … python webhook_admin.py delete <id>
"""
import json
import os
import sys

import requests

BASE_URL = os.environ.get("DANADRESSE_BASE_URL", "https://api.danadresse.dk")


def login() -> str:
    r = requests.post(f"{BASE_URL}/api/v1/auth/login", timeout=10, json={
        "email": os.environ["DANADRESSE_EMAIL"],
        "password": os.environ["DANADRESSE_PASSWORD"],
    })
    if not r.ok:
        raise SystemExit(f"login fejlede: {r.status_code} {r.json().get('error', {}).get('code')}")
    return r.json()["access_token"]  # JWT, gyldig i 24 timer


def main(argv: list[str]) -> None:
    if not argv or argv[0] not in ("list", "create", "delete"):
        raise SystemExit(__doc__)
    auth = {"Authorization": f"Bearer {login()}"}

    if argv[0] == "list":
        r = requests.get(f"{BASE_URL}/api/v1/webhooks", headers=auth, timeout=10)
    elif argv[0] == "create":
        url, entities = argv[1], argv[2:]
        r = requests.post(f"{BASE_URL}/api/v1/webhooks", headers=auth, timeout=10,
                          json={"url": url, "entities": entities})
    else:
        r = requests.delete(f"{BASE_URL}/api/v1/webhooks/{argv[1]}", headers=auth, timeout=10)

    body = r.json()
    if not r.ok:
        err = body.get("error") or {}
        raise SystemExit(f"{r.status_code} {err.get('code')}: {err.get('message')}")
    if argv[0] == "create":
        # Hemmeligheden vises kun nu. Gem den, fx i din secrets manager.
        print("GEM DENNE HEMMELIGHED:", body["secret"], file=sys.stderr)
        body.pop("secret")
    print(json.dumps(body, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main(sys.argv[1:])

6. Hvad du modtager

Når en entitet, du følger, har ændret sig, får du ét POST pr. entitet:

HTTP#delivery
POST /hooks/danadresse HTTP/1.1
Host: app.firma.dk
Content-Type: application/json
User-Agent: danadresse-webhooks/1.0
X-Danadresse-Entity: adresse
X-Danadresse-Signature: 5d1f0c…(hex HMAC-SHA256 af hele body)

{"entity": "adresse", "sekvensnummer": 207922555, "count": 37, "changes": ["0a3f50ae-b4ee-32b8-e044-0003ba298018", "…"], "timestamp": "2026-10-15T13:08:11.512345+00:00"}
FeltIndhold
entityEntiteten, der har ændret sig
sekvensnummerDet højeste sekvensnummer i denne runde. Brug det som cursor
countAntal ændringer i denne runde
changesId'erne på de ændrede rækker, højst 1.000. Er count større, så hent resten med hændelser. For vejstykke står der kun kommunekoden, så brug hændelser til at finde vejen
timestampHvornår leveringen blev sendt (UTC)

Leveringerne sendes, når vores synkronisering har hentet nye ændringer. Det sker omtrent én gang i timen. Der er ikke en knap til at sende en testlevering. Test i stedet dit endpoint ved at signere en eksempel-body selv, som vist i Verificér webhook-signaturen.

7. Hvad du gør med en levering

Hændelserne bag en levering ligger allerede i hændelsesloggen, når den sendes. Den normale reaktion er derfor: verificér signaturen, gem leveringen, svar 204, og hent så hændelserne for den entitet med /replikering/haendelser?entitet=… fra din egen cursor + 1 op til leveringens sekvensnummer. Hele forløbet er: fuldt udtræk én gang, derefter hændelser fra din cursor, startet af webhooken og af et cron-job som sikkerhedsnet. Modtageren og workeren står i Webhooks: idempotens og fejl.

Relaterede guides

Markdown-kilde Fejl i guiden? Skriv til os API-reference (Swagger)

Prøv det med en gratis nøgle

2 000 kald/måned på gratisplanen. Intet kreditkort.

Opret konto →