NaboPortalen API

Offentligt API og webhooks for foreninger på NaboPortalen. Base-URL er foreningens eget domæne: https://{forening}.naboportalen.dk/api/v1. API-nøgler oprettes af bestyrelsen under Foreningen → Integrationer, og hvert endpoint kræver præcis de scopes der står på det. API'et er inkluderet i abonnementet.

Sandkasse: Prøv API'et af på https://sandbox.naboportalen.dk/api/v1 – en testforening med fiktive data, som nulstilles hver nat. Bestyrelsen opretter en sandkasse-nøgle (nbp_test_…) under Foreningen → Integrationer → Sandkasse til udviklere. Bygger I en integration uden at høre til en forening, så skriv til info@naboportalen.dk.

Sikkerhed i korte træk: én nøgle = én forening; scopes uden arv; alle svar bærer X-Request-Id; fejl er RFC 9457 Problem Details; skrive-kald kan sendes med Idempotency-Key; webhooks signeres med HMAC-SHA256 i Naboportalen-Signature. Sårbarheder meldes til security@naboportalen.dk.
Verificér en webhook-signatur
signed_payload = t + "." + raw_body
expected       = HMAC_SHA256(secret, signed_payload)   // hex
ok             = constant_time_equals(expected, v1) && |now - t| <= 300 s

// PHP
[$t, $v1] = array_map(fn ($p) => explode('=', $p, 2)[1], explode(',', $_SERVER['HTTP_NABOPORTALEN_SIGNATURE']));
$raw = file_get_contents('php://input');
$expected = hash_hmac('sha256', "$t.$raw", $secret);
if (!hash_equals($expected, $v1) || abs(time() - (int) $t) > 300) { http_response_code(400); exit; }

// Node
const [t, v1] = sig.split(',').map(p => p.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)) && Math.abs(Date.now()/1000 - t) <= 300;
Idempotency-Key på skrive-kald

Send en unik streng (1-255 ASCII-tegn) i Idempotency-Key. Samme nøgle + samme request inden for 24 timer returnerer det gemte svar med Idempotent-Replayed: true. Samme nøgle med en anden request giver 422 idempotency_key_reused.