Het kortste antwoord op de vraag "wat is een webhook" is dit: wanneer er in een systeem een gebeurtenis plaatsvindt, stuurt dat systeem automatisch een HTTP-verzoek naar een URL die jij hebt opgegeven. Dus in plaats van voortdurend te vragen "is er iets nieuws gebeurd?", belt de andere kant jou op het moment dat de gebeurtenis optreedt. Deze kleine omkering vormt de ruggengraat van moderne integraties: betaalproviders, Git-platforms, Discord, e-maildiensten en bijna elk SaaS-product werken met webhooks.
Het verschil tussen polling en webhooks
In het polling-model gaat jouw applicatie met vaste tussenpozen naar de externe API en vraagt "is er iets veranderd?". Meestal is het antwoord "nee", dus het grootste deel van je verzoeken is verspild. Zelfs als je elke minuut vraagt, krijg je gemiddeld 30 seconden vertraging tussen de gebeurtenis en het moment dat je ervan hoort.
Met het webhook-model werk je gebeurtenisgedreven: zodra er iets verandert, pusht de andere kant onmiddellijk data naar jouw endpoint. De voordelen zijn duidelijk:
- Realtime: je hoort van een gebeurtenis op het moment dat die plaatsvindt — de latentie is milliseconden, geen seconden.
- Efficiëntie: in plaats van duizenden verspilde verzoeken wordt er alleen verkeer gegenereerd voor echte gebeurtenissen.
- Schaalbaarheid: je server raakt niet uitgeput door overbodige queries en je loopt niet tegen rate limits aan.
Polling heeft nog steeds zijn plaats: wanneer de andere kant geen webhooks aanbiedt, of wanneer gegarandeerde levering een harde eis is. Maar als je de keuze hebt, is de gebeurtenisgedreven aanpak bijna altijd schoner.
Hoe ziet een webhook-verzoek eruit?
Een webhook-verzoek is eigenlijk gewoon een doodnormaal HTTP-POST-verzoek. Het verzendende systeem draagt de details van de gebeurtenis, meestal in een JSON-body. Een betalingsbevestiging kan er bijvoorbeeld zo uitzien:
POST /webhooks/payment HTTP/1.1
Host: jouw-site.nl
Content-Type: application/json
X-Signature: t=1719500000,v1=4a9f...c2
{
"event": "payment.succeeded",
"data": {
"id": "pay_8sK2",
"amount": 4900,
"currency": "EUR",
"customer": "cus_12"
}
}
Jouw kant ontvangt dit verzoek, leest de body, voert werk uit op basis van het event-type (bevestigt de bestelling, stuurt een e-mail, geeft de gebruiker een rol) en stuurt snel 200 OK terug naar de verzender. Het cruciale punt hier: reageer snel. Schuif zwaar werk (rapporten genereren, externe API's aanroepen) door naar een wachtrij; laat de webhook niet wachten.
Een webhook-ontvanger bouwen met Laravel
In de praktijk is het schrijven van een webhook-endpoint heel eenvoudig. Met Laravel zijn een route en een controller voldoende:
// routes/web.php
Route::post('/webhooks/payment', [WebhookController::class, 'handle'])
->withoutMiddleware([VerifyCsrfToken::class]);
Let op: webhooks komen van externe systemen en dragen geen sessiecookie; daarom moet je de CSRF-bescherming voor deze route uitschakelen. In de controller verwerken we de gebeurtenis:
public function handle(Request $request)
{
$payload = $request->all();
if ($payload['event'] === 'payment.succeeded') {
ProcessPayment::dispatch($payload['data']);
}
return response()->json(['ok' => true]);
}
Hier dragen we het werk over aan een queue-job met ProcessPayment::dispatch(...), zodat de controller direct reageert en het zware werk op de achtergrond draait.
Handtekeningverificatie: het hart van webhook-beveiliging
Omdat je webhook-URL publiek bereikbaar is, moet je voorkomen dat een kwaadwillende valse verzoeken stuurt. De meeste providers ondertekenen de body met een gedeeld geheim via HMAC en sturen de handtekening in een header (bijv. X-Signature). Jij herberekent de handtekening met dezelfde sleutel en vergelijkt:
$signature = $request->header('X-Signature');
$expected = hash_hmac('sha256', $request->getContent(), $secret);
if (! hash_equals($expected, $signature)) {
abort(403, 'Ongeldige handtekening');
}
Cruciaal detail: bereken de handtekening over de ruwe body (getContent()), niet over de geparste array, want het opnieuw serialiseren van de JSON kan verschillen op byte-niveau introduceren. Gebruik ook hash_equals(), want == is kwetsbaar voor timing-aanvallen.
Betrouwbaarheid: nieuwe pogingen en idempotentie
In de echte wereld gaat niet altijd alles soepel. Als je server even niet kan reageren, doet de meeste providers een nieuwe poging. Dat betekent dat dezelfde gebeurtenis meer dan eens kan binnenkomen. Daarom moeten je handlers idempotent zijn:
- Elke gebeurtenis heeft een uniek
id-veld; sla het op. - Komt hetzelfde
idopnieuw binnen, doe het werk dan niet nog eens — stuur gewoon200terug. - Stuur een snelle
2xx; een trage reactie zorgt ervoor dat de provider je als mislukt beschouwt en onnodig opnieuw probeert.
Om webhooks tijdens de ontwikkeling te testen, kun je je lokale server naar buiten openstellen met een tunneltool zoals ngrok en testgebeurtenissen activeren vanuit het dashboard van de provider.
Veelgestelde vragen
Wat is het verschil tussen een webhook en een API?
Een API is meestal een "pull"-model waarbij jij het verzoek start: jij vraagt, jij krijgt een antwoord. Een webhook is een "push"-model: als er een gebeurtenis plaatsvindt, stuurt de andere kant data naar jou. De meeste integraties gebruiken beide samen.
Hoe houd ik mijn webhook-URL veilig?
Gebruik altijd HTTPS, verifieer de handtekening van elk inkomend verzoek, zet indien mogelijk het IP-bereik van de provider op een allowlist, en bewaar het geheim in een omgevingsvariabele (.env) in plaats van in de code.
Wat doe ik als een webhook niet binnenkomt?
Bekijk eerst de leveringslogs van de provider; de meeste tonen mislukte pogingen en de geretourneerde HTTP-code. Zorg ervoor dat je endpoint echt 200 teruggeeft door de ruwe body te loggen, en verstuur de gebeurtenis indien nodig handmatig opnieuw vanuit het dashboard.
Heb je een gebeurtenisgedreven integratie nodig? Ik help je graag bij het bouwen van veilige webhook-flows voor betalingen, Discord, Git of je eigen systemen. Neem contact met me op en laten we het over je project hebben.