Hopp til innhold

Utvikler-API

Webhooks-API

Slutt å polle. Få endringer i Brønnøysund-registrene sendt til ditt eget endepunkt: signert med HMAC-SHA256, SSRF-validert og med automatiske nye forsøk og vern mot gjentaksangrep.

Hva er webhooks-API-et?

Med POST /api/v1/subscriptions kan du få de samme endringene fra endringsarkivet som /v1/changes gir, sendt til ditt eget HTTPS-endepunkt i stedet for å polle. Hver levering signeres med HMAC-SHA256, og abonnementet krever en API-nøkkel med scopet subscribe:webhooks (Pro-nivået).

Hvordan verifiserer jeg signaturen på en webhook?

Hver levering bærer en X-Apier-Signature-header på Stripe-formen t=<unix>,v1=<hex>. Du beregner HMAC-SHA256 over strengen "<timestamp>.<body>" med webhookhemmeligheten din, avviser leveringer med et foreldet tidsstempel (standardtoleranse: 300 sekunder) for å hindre gjentaksangrep (replay), og sammenligner v1 i konstant tid. Hemmeligheten i klartekst returneres KUN én gang, når abonnementet opprettes. Lagre den da; den kan ikke hentes igjen.

Verifiser signaturen over det rå innholdet i forespørselen (ikke en versjon som er serialisert på nytt), avvis et foreldet tidsstempel før du sjekker HMAC-en (vern mot gjentaksangrep), og sammenlign i konstant tid for å unngå tidsbaserte angrep. Et kodeeksempel ligger lenger ned.

Hvilke hendelser kan jeg abonnere på?

I v1 sender Apier én type webhookhendelse: accounts.updated. Den betyr at en norsk virksomhets årsregnskapsstatus endret seg i det åpne Regnskapsregisteret i Brønnøysund. Hendelsen bygger på endringsarkivet, så du velger den med et abonnementsfilter på endringsradens source og entity_type (f.eks. { source: "brreg", entity_type: "annual_accounts" }). Endringsarkivet sporer flere kilder, men webhookleveringer er Brønnøysund-baserte i dag.

  • accounts.updated: den eneste hendelsestypen i v1. En virksomhets årsregnskapsstatus endret seg i Regnskapsregisteret i Brønnøysund (ny innsending, eller siste regnskapsår gikk fram).
  • Filter: Velg hendelser med et JSONB-filter på source, entity_type, change_type og datointervall (f.eks. { source: "brreg", entity_type: "annual_accounts" }).
  • Endringsarkivet: sporer flere kilder (Altinn-skjemaer, DigDir-policyer, Norges Bank-kurser); flere hendelsestyper kommer etter hvert som de kildene tas i bruk.

Hvordan kommer jeg i gang?

Opprett et abonnement med POST /api/v1/subscriptions: Oppgi webhook_url (HTTPS) og et filter, og motta webhookhemmeligheten i svaret. URL-en SSRF-valideres både ved opprettelse og ved hver levering: Private CIDR-er, verter for metadataendepunkter og .local-suffikser avvises. Verifiser signaturen på hver levering før du stoler på innholdet.

Hvordan håndterer jeg feil og leveringsforsøk?

En levering gjøres med inntil 7 forsøk totalt, ett umiddelbart og deretter seks nye forsøk med eksponentielt økende ventetid (1m, 5m, 15m, 1h, 6h, 24h), før den gis opp. Et abonnement deaktiveres automatisk etter 6 påfølgende 4xx-svar (408 og 429 teller ikke). Svar med 2xx raskt og gjør tungt arbeid asynkront, så et tregt endepunkt ikke utløser unødvendige nye forsøk.

  • Eksponentielt økende ventetid: 7 forsøk totalt. Ett umiddelbart, deretter seks nye forsøk etter planen 1m, 5m, 15m, 1h, 6h, 24h før leveringen gis opp.
  • Automatisk deaktivering: Etter 6 påfølgende 4xx-svar (408 og 429 unntatt) deaktiveres abonnementet, med en grunn du kan inspisere.
  • Idempotens: Samme endring kan leveres mer enn én gang ved et nytt forsøk; fjern duplikater på din side ved hjelp av leverings-ID-en.

Kodeeksempel

Opprett et abonnement, og verifiser signaturen på hver levering før du stoler på innholdet.

# Opprett et webhookabonnement. Hemmeligheten returneres KUN her.
curl -X POST \
  -H "Authorization: Bearer $APIER_API_KEY" \
  -H "Content-Type: application/json" \
  "https://www.apier.no/api/v1/subscriptions" \
  -d '{
    "webhook_url": "https://example.com/apier/webhook",
    "filter": { "source": "brreg", "change_type": "updated" }
  }'
import crypto from "node:crypto";

// X-Apier-Signature: "t=<unix_seconds>,v1=<hex>" (Stripe-style).
// Reject malformed headers AND stale timestamps (replay) BEFORE the HMAC.
export function verifyApierWebhook(rawBody, signatureHeader) {
  const parts = Object.fromEntries(
    (signatureHeader ?? "").split(",").map((p) => p.trim().split("=")),
  );
  const ts = Number(parts.t);
  const sig = parts.v1;
  // Malformed / missing header — never throw, just reject.
  if (!Number.isInteger(ts) || !sig) return false;
  // Replay window: reject deliveries older/newer than 300s (Apier default).
  if (Math.abs(Math.floor(Date.now() / 1000) - ts) > 300) return false;
  const expected = crypto
    .createHmac("sha256", process.env.APIER_WEBHOOK_SECRET)
    .update(ts + "." + rawBody, "utf8")
    .digest("hex");
  // Length guard first — timingSafeEqual throws on unequal-length buffers.
  if (expected.length !== sig.length) return false;
  return crypto.timingSafeEqual(
    Buffer.from(expected, "utf8"),
    Buffer.from(sig, "utf8"),
  );
}

Hvor finner jeg API-referansen?

Den fullstendige API-referansen for /api/v1/subscriptions ligger i OpenAPI 3.1-spesifikasjonen på /openapi.json og i utviklerdokumentasjonen under /docs/api. Webhookflyten steg for steg er beskrevet i guiden /docs/guides/webhooks.

Relaterte utviklersider