Skip to content
Apier

Utvikler-API

Webhooks-API

Slutt å polle. Få endringer i Brønnøysund-registrene pushet til ditt eget endepunkt — signert med HMAC-SHA256, SSRF-validert og med innebygd backoff og replay-vern.

Hva er webhooks-API-et?

Med POST /api/v1/subscriptions kan du få de samme endringsarkiv-deltaene som /v1/changes pushet til ditt eget HTTPS-endepunkt i stedet for å polle. Hver levering signeres med HMAC-SHA256, og abonnementet krever en API-nøkkel med subscribe:webhooks-scope (Pro-tier).

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 webhook-hemmeligheten din, avviser leveringer med et foreldet tidsstempel (standard toleranse: 300 sekunder) for å hindre replay, og sammenligner v1 med en konstant-tids-sammenligning. Hemmeligheten i klartekst returneres KUN én gang når abonnementet opprettes — lagre den da; den kan ikke hentes igjen.

Verifiser signaturen over den request-bodyen (ikke en re-serialisert versjon), avvis et foreldet tidsstempel før du sjekker HMAC-en (replay-vern), og bruk en konstant-tids-sammenligning for å unngå timing-lekkasjer. Et kodeeksempel ligger lenger ned.

Hvilke hendelser kan jeg abonnere på?

I v1 sender Apier én webhook-hendelsestype: accounts.updated — en norsk virksomhets årsregnskap-status endret seg i det åpne Brønnøysund Regnskapsregisteret. Hendelsen rir 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 webhook-leveringer er Brønnøysund-baserte i dag.

  • accounts.updated — den eneste hendelsestypen i v1: en virksomhets årsregnskap-status endret seg i Brønnøysund Regnskapsregisteret (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 går live.

Hvordan kommer jeg i gang?

Opprett et abonnement med POST /api/v1/subscriptions: oppgi webhook_url (HTTPS), et filter og motta webhook-hemmeligheten i svaret. URL-en SSRF-valideres både ved opprettelse og ved hver levering — private CIDR-er, metadata-endepunkt-verter 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, deretter seks nye forsøk med eksponentiell backoff (1m, 5m, 15m, 1h, 6h, 24h) — før den forlates. 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.

  • Eksponentiell backoff — 7 forsøk totalt: ett umiddelbart, deretter seks nye forsøk på planen 1m, 5m, 15m, 1h, 6h, 24h før levering forlates.
  • Auto-deaktivering — etter 6 påfølgende 4xx-svar (408 og 429 unntatt) deaktiveres abonnementet, med en grunn du kan inspisere.
  • Idempotens — samme delta kan leveres mer enn én gang ved et nytt forsøk; avdupliser på leverings-ID-en din side.

Kodeeksempel

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

# Opprett et webhook-abonnement. 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. Webhook-flyten steg for steg er beskrevet i guiden /docs/guides/webhooks.

Relaterte utviklersider