Naar de inhoud

Techniek · 7 november 2026 · 6 min leestijd

Webhooks eenvoudig uitgelegd: handtekening, herhaling, beveiliging

Wat een webhook is, hoe hij verschilt van een API-opvraag en hoe u handtekening, herhaling en dubbele leveringen behandelt. Met curl en Node.

Twee wegen vergeleken: bij de API-opvraag vraagt uw systeem steeds opnieuw na, bij de webhook roept de afzender uw adres eenmalig aan zodra er iets gebeurt
Afbeelding: Whovisit · Afbeelding groot bekijken

Een webhook is een telefoontje in de andere richting. In plaats van dat uw systeem om de paar minuten bij een dienst navraagt of er iets is gebeurd, roept de dienst een adres bij u aan zodra het gebeurt. Dat bespaart opvragen en levert nieuws sneller. Daarvoor moet u een ontvanger bieden en beveiligen. Dit artikel legt beide uit.

Webhook of API-opvraag

API-opvraag (polling)Webhook
Wie start?Uw systeemDe afzender
Wanneer?Volgens schema, bijvoorbeeld elk uurZodra een gebeurtenis plaatsvindt
InspanningVerzoeken ook zonder nieuwsEen bereikbare ontvanger nodig
Goed voorRapporten, afstemming, naladenMelding, doorsturen
RisicoVertraging tot de volgende opvraagBericht gaat verloren als de ontvanger niet antwoordt

In de praktijk combineren velen beide: de webhook meldt de gebeurtenis, de API haalt zo nodig de details op of sluit gaten na een storing.

Hoe een levering verloopt

  1. Een gebeurtenis vindt plaats, bijvoorbeeld „betaling ontvangen“.
  2. De afzender stuurt een POST met JSON naar uw opgegeven adres (HTTPS).
  3. Uw ontvanger controleert de handtekening, onthoudt het bericht en antwoordt snel met een code uit het 2xx-bereik.
  4. Het eigenlijke werk doet uw systeem daarna.

Stap 3 is belangrijk. Stripe raadt in de webhookdocumentatie aan meteen een 2xx-code terug te geven voordat bewerkelijke logica draait die een time-out kan veroorzaken. Wie eerst de factuur boekt en dan antwoordt, riskeert onnodige herhalingen.

Handtekening: hoe u de herkomst controleert

Uw adres is openbaar bereikbaar. Iedereen zou dus verzonnen berichten ernaartoe kunnen sturen. Daarom ondertekenen serieuze afzenders elk bericht met een gedeeld geheim. Gebruikelijk is een HMAC met SHA-256 over de inhoud, vaak samen met een tijdstempel. GitHub beschrijft de werkwijze zo: HMAC-SHA256 over de inhoud, hexadecimaal weergegeven, met de koptekst X-Hub-Signature-256. De vergelijking moet gebeuren met een functie met constante looptijd, zodat aanvallers uit antwoordtijden niets kunnen afleiden.

Belangrijke punten voor uw ontvanger:

  • Ruwe inhoud gebruiken. Bereken de handtekening over de ontvangen bytes, niet over de opnieuw geserialiseerde JSON. Al een andere regeleinde verandert de handtekening.
  • Tijdstempel controleren. Stripe gebruikt een tijdstempel in de handtekening en een standaardtolerantie van vijf minuten om herhalingsaanvallen te voorkomen. Een tolerantie van 0 schakelt de controle uit.
  • Geheim geheim houden. Niet in de code, niet in de repository, maar in een omgevingsvariabele of een kluis.

Open-sourcerichtlijnen voor afzenders en ontvangers bundelt het initiatief Standard Webhooks. Hoe elke aanbieder zijn handtekening concreet opbouwt, staat in diens documentatie.

Herhalingen en dubbele levering

Antwoordt uw ontvanger niet of met een foutcode, dan probeert de afzender het opnieuw. Stripe herhaalt volgens de eigen documentatie met toenemende tussenpozen tot drie dagen lang. Andere aanbieders hebben andere regels, sommige schakelen de webhook uit na te veel mislukkingen. Hieruit volgt:

  • Reken op dubbelen. Dezelfde gebeurtenis kan meermaals aankomen, ook als uw ontvanger correct werkt. Stripe wijst daar uitdrukkelijk op.
  • Maak de verwerking idempotent. Dat betekent: wie hetzelfde bericht twee keer verwerkt, maakt geen tweede resultaat. Sla de identificatie van de gebeurtenis op en sla bekende over.
  • Vertrouw niet op de volgorde. Gebeurtenissen kunnen verwisseld aankomen. Controleer bij twijfel de actuele toestand via de API.

Beveiliging aan beide kanten

Voor ontvangers: alleen HTTPS, handtekening en leeftijd controleren, grootte van het bericht beperken, geen opdrachten uit de inhoud uitvoeren. Behandel de inhoud als elke invoer van buiten.

Voor afzenders die adressen van klanten aanroepen: wie webhooks aanbiedt, roept adressen aan die anderen hebben ingevoerd. Dat is een klassieke weg voor server-side request forgery. De aanvaller voert een intern adres in. De server roept het aan vanuit het beschermde netwerk. De OWASP-beschrijving noemt als doelen bijvoorbeeld interne diensten en metadata-adressen van cloudaanbieders. Zinvolle beschermende maatregelen:

  • alleen https:// toestaan,
  • het adres bij het invoeren en vóór elke aanroep omzetten en private, lokale en gereserveerde bereiken afwijzen,
  • doorverwijzingen niet blindelings volgen,
  • korte tijdlimieten instellen.

Voorbeeld: testbericht met curl versturen

Met curl en openssl kunt u uw ontvanger testen zonder de echte afzender. Het script verstuurt een ondertekend bericht, zoals een afzender met tijdstempel het zou sturen:

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"

Voorbeeld: een kleine ontvanger in Node.js

const http = require('http')
const crypto = require('crypto')
const GEHEIMNIS = process.env.WEBHOOK_GEHEIMNIS || 'test-geheimnis'
const gesehen = new Set() // in werkelijkheid: 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)

De ontvanger controleert handtekening en leeftijd, slaat bekende identificaties over en antwoordt meteen. Voor productie hebt u bovendien HTTPS, een groottebeperking en een wachtrij voor de verdere verwerking nodig.

Waar webhooks gangbaar zijn

Betaaldiensten, codeplatforms en formuliertools melden gebeurtenissen per webhook. Ook bezoekersherkenning kan het: meldt het systeem een bedrijf dat uw pagina bezoekt, dan komt het rechtstreeks terecht in een hulpmiddel naar keuze. Hoe dat naar een verkoopsysteem leidt, laat Websitebezoeken naar het CRM brengen: 4 wegen vergeleken zien. Het alternatief per opvraag beschrijft Welke bedrijven bezoeken mijn website?. Welke gegevens daarbij stromen, legt Hoe werkt bedrijfsherkenning? uit.

Veelgestelde vragen

Wat is het verschil tussen webhook en API? Bij de API vraagt uw systeem na, bij de webhook meldt de afzender zich uit zichzelf. Beide zijn te combineren.

Wat gebeurt er als mijn ontvanger uitvalt? De meeste afzenders herhalen de levering gedurende een bepaalde periode. Hoe lang en hoe vaak, staat in de documentatie van de aanbieder. Voor gaten moet u een afstemming via de API inplannen.

Volstaat het om het afzenderadres te controleren? Nee. Afzenderadressen laten zich voorwenden en veranderen. Betrouwbaar is alleen de handtekening met een geheim dat alleen u en de afzender kennen.

Verder lezen