Gå til indhold

Teknik · 7. november 2026 · 5 min læsning

Webhooks forklaret enkelt: signatur, gentagelse, sikkerhed

Hvad en webhook er, hvordan den adskiller sig fra API-opslag, og hvordan du håndterer signatur, gentagelse og dubletter. Med curl og Node.

To veje i sammenligning: Ved API-opslag spørger dit system igen og igen, ved webhook kalder afsenderen din adresse én gang, så snart noget sker
Grafik: Whovisit · Se grafikken i stor størrelse

En webhook er et opkald i den anden retning. I stedet for at dit system hos en tjeneste med få minutters mellemrum spørger, om der er sket noget, kalder tjenesten en adresse hos dig, så snart det sker. Det sparer opslag og leverer nyheder hurtigere. Til gengæld skal du stille en modtager til rådighed og sikre den. Denne artikel forklarer begge dele.

Webhook eller API-opslag

API-opslag (polling)Webhook
Hvem starter?Dit systemAfsenderen
Hvornår?Efter tidsplan, for eksempel hver timeSå snart en begivenhed indtræffer
IndsatsForespørgsler også uden nyhederEn tilgængelig modtager er nødvendig
Godt tilRapporter, afstemning, efterhentningBesked, videresendelse
RisikoForsinkelse til næste opslagBeskeden går tabt, hvis modtageren ikke svarer

I praksis kombinerer mange begge: Webhooken melder begivenheden, API'en henter om nødvendigt detaljerne eller lukker huller efter et udfald.

Sådan forløber en levering

  1. En begivenhed indtræffer, for eksempel „betaling modtaget“.
  2. Afsenderen sender en POST med JSON til din angivne adresse (HTTPS).
  3. Din modtager tjekker signaturen, husker beskeden og svarer hurtigt med en kode fra 2xx-området.
  4. Selve arbejdet klarer dit system derefter.

Trin 3 er vigtigt. Stripe anbefaler i webhook-dokumentationen straks at returnere en 2xx-kode, før der kører omstændelig logik, som kan udløse en timeout. Den, der først bogfører fakturaen og derefter svarer, risikerer unødvendige gentagelser.

Signatur: sådan tjekker du oprindelsen

Din adresse kan nås offentligt. Enhver kunne altså sende opdigtede beskeder til den. Derfor signerer seriøse afsendere hver besked med en fælles hemmelighed. Almindeligt er en HMAC med SHA-256 over indholdet, ofte sammen med et tidsstempel. GitHub beskriver fremgangsmåden sådan: HMAC-SHA256 over indholdet, vist hexadecimalt, med headeren X-Hub-Signature-256. Sammenligningen skal ske med en funktion med konstant løbetid, så angribere ikke kan udlede noget af svartider.

Vigtige punkter for din modtager:

  • Brug råindholdet. Beregn signaturen over de modtagne bytes, ikke over den påny serialiserede JSON. Allerede et andet linjeskift ændrer signaturen.
  • Tjek tidsstemplet. Stripe bruger et tidsstempel i signaturen og en standardtolerance på fem minutter for at forhindre gentagelsesangreb. En tolerance på 0 slår kontrollen fra.
  • Hold hemmeligheden hemmelig. Ikke i koden, ikke i repositoriet, men i en miljøvariabel eller et pengeskab.

Open source-retningslinjer for afsendere og modtagere samler initiativet Standard Webhooks. Hvordan hver udbyder konkret danner sin signatur, står i dennes dokumentation.

Gentagelser og dobbeltlevering

Svarer din modtager ikke eller med en fejlkode, forsøger afsenderen igen. Stripe gentager for eksempel efter egen dokumentation med stigende intervaller i op til tre dage. Andre udbydere har andre regler, nogle slår webhooken fra efter for mange fejlslag. Heraf følger:

  • Regn med dubletter. Den samme begivenhed kan ankomme flere gange, også selv om din modtager arbejder korrekt. Stripe henviser udtrykkeligt til det.
  • Gør behandlingen idempotent. Det betyder: Den, der behandler den samme besked to gange, frembringer intet andet resultat. Gem begivenhedens identifikator og spring det kendte over.
  • Stol ikke på rækkefølgen. Begivenheder kan ankomme byttet om. Tjek i tvivlstilfælde den aktuelle tilstand via API.

Sikkerhed på begge sider

For modtagere: kun HTTPS, tjek signatur og alder, begræns beskedens størrelse, udfør ingen kommandoer fra indholdet. Behandl indholdet som ethvert input udefra.

For afsendere, der kalder kunders adresser: Den, der tilbyder webhooks, kalder adresser, som andre har indtastet. Det er en klassisk vej for server-side request forgery. Angriberen indtaster en intern adresse. Serveren kalder den indefra det beskyttede net. OWASP-beskrivelsen nævner som mål for eksempel interne tjenester og metadataadresser hos cloududbydere. Fornuftige beskyttelsesforanstaltninger:

  • tillad kun https://,
  • opløs adressen ved indtastningen og før hvert kald og afvis private, lokale og reserverede områder,
  • følg ikke videresendelser blindt,
  • sæt korte tidsgrænser.

Eksempel: send en testbesked med curl

Med curl og openssl kan du teste din modtager uden den ægte afsender. Scriptet sender en signeret besked, som en afsender med tidsstempel ville sende den:

GEHEIMNIS="test-geheimnis"
ZEIT=$(date +%s)
INHALT='{"id":"evt_1","typ":"besuch.begonnen","firma":"beispiel-firma.example"}'
SIGNATUR="sha256=$(printf '%s.%s' "$ZEIT" "$INHALT" | openssl dgst -sha256 -hmac "$GEHEIMNIS" | sed 's/^.* //')"

curl -X POST http://localhost:3000/webhook \
  -H "Content-Type: application/json" \
  -H "X-Zeit: $ZEIT" \
  -H "X-Signatur: $SIGNATUR" \
  -d "$INHALT"

Eksempel: en lille modtager i Node.js

const http = require('http')
const crypto = require('crypto')
const GEHEIMNIS = process.env.WEBHOOK_GEHEIMNIS || 'test-geheimnis'
const gesehen = new Set() // i virkeligheden: database

http.createServer((req, res) => {
  if (req.method !== 'POST' || req.url !== '/webhook') { res.writeHead(404); return res.end() }
  const teile = []
  req.on('data', t => teile.push(t))
  req.on('end', () => {
    const roh = Buffer.concat(teile).toString('utf8')
    const zeit = req.headers['x-zeit'] || ''
    const erwartet = 'sha256=' + crypto.createHmac('sha256', GEHEIMNIS).update(zeit + '.' + roh).digest('hex')
    const erhalten = String(req.headers['x-signatur'] || '')
    const a = Buffer.from(erwartet), b = Buffer.from(erhalten)
    const echt = a.length === b.length && crypto.timingSafeEqual(a, b)
    const frisch = Math.abs(Date.now() / 1000 - Number(zeit)) < 300
    if (!echt || !frisch) { res.writeHead(401); return res.end() }
    const ereignis = JSON.parse(roh)
    if (!gesehen.has(ereignis.id)) { gesehen.add(ereignis.id); console.log('neu:', ereignis.typ) }
    res.writeHead(200); res.end('ok')
  })
}).listen(3000)

Modtageren tjekker signatur og alder, springer kendte identifikatorer over og svarer straks. Til drift har du desuden brug for HTTPS, en størrelsesbegrænsning og en kø til viderebehandlingen.

Hvor webhooks er udbredte

Betalingstjenester, kodeplatforme og formularværktøjer melder begivenheder via webhook. Også besøgergenkendelse kan det: Melder systemet en virksomhed, der besøger din side, havner den direkte i et værktøj efter dit valg. Hvordan det fører ind i et salgssystem, viser Hjemmesidebesøg ind i CRM. Alternativet via opslag beskriver Hvilke virksomheder besøger min hjemmeside?. Hvilke data der derved flyder, forklarer Hvordan fungerer virksomhedsgenkendelse?.

Ofte stillede spørgsmål

Hvad er forskellen mellem webhook og API? Ved API'en spørger dit system, ved webhooken melder afsenderen sig selv. Begge dele kan kombineres.

Hvad sker der, hvis min modtager falder ud? De fleste afsendere gentager leveringen over en vis periode. Hvor længe og hvor ofte, står i udbyderens dokumentation. Til huller bør du planlægge en afstemning via API.

Rækker det at tjekke afsenderadressen? Nej. Afsenderadresser lader sig forfalske og ændrer sig. Pålideligt er kun signaturen med en hemmelighed, som kun du og afsenderen kender.

Læs videre