Heb je webhooks geïntegreerd en zie je dezelfde gebeurtenis meer dan eens binnenkomen, dan heb je geen bug gevonden. Je bent gestuit op iets wat eigen is aan webhooks in het algemeen. Deze gids legt uit waarom dubbele webhook-afleveringen voorkomen en hoe je daar correct mee omgaat met idempotentie.
Webhooks garanderen geen exactly-once delivery
Dit is het kernpunt om te begrijpen: er bestaat geen exactly-once delivery-garantie voor webhooks. Niet bij AhaSend, niet bij Stripe, nergens. Dit is geen tekortkoming die iemand niet heeft weten op te lossen. Het is een inherente eigenschap van het afleveren van berichten over een onbetrouwbaar netwerk aan een endpoint waar de verzender geen controle over heeft.
De reden komt neer op een simpel probleem. Als AhaSend je endpoint een webhook stuurt en je server verwerkt die, dan hebben wij een bevestiging nodig dat je hem hebt ontvangen. Bereikt die bevestiging ons nooit, dan kunnen we onmogelijk weten of je de gebeurtenis hebt verwerkt of niet. De veilige keuze is opnieuw proberen, want het alternatief, de gebeurtenis laten vallen, betekent dat je iets belangrijks zou kunnen missen, zoals een afleveringsfout of een spamklacht.
Dus proberen we het opnieuw. En af en toe betekent dat je endpoint een gebeurtenis ontvangt die het al heeft gezien.
Hoe een duplicaat precies ontstaat
Er zijn twee veelvoorkomende scenario's die ertoe leiden dat dezelfde webhook meer dan eens wordt afgeleverd.
Het eerste is een tijdelijk netwerkprobleem. Je systeem ontvangt de webhook en verwerkt die correct, maar de bevestiging terug naar AhaSend gaat ergens op het netwerk verloren. Vanaf onze kant lijkt de aflevering mislukt, dus proberen we het opnieuw. Vanaf jouw kant heb je dezelfde gebeurtenis nu twee keer ontvangen.
Het tweede is een timeout. AhaSend wacht maximaal 10 seconden op een reactie van je endpoint. Doet je server er langer over, dan sluiten we de verbinding en zetten we de gebeurtenis in de wachtrij voor een latere retry, ook al heeft je applicatie de verwerking misschien allang afgerond. Komt die retry binnen, dan is dat een duplicaat.
In beide gevallen is er niets misgegaan met het systeem. De webhook-aflevering gedraagt zich precies zoals bedoeld: het prioriteert dat je elke gebeurtenis ontvangt boven het gemak om er nooit een dubbel te zien.
De oplossing: idempotentie met de webhook-id header
De juiste manier om hiermee om te gaan is je webhook-verwerking idempotent maken, wat betekent dat een gebeurtenis twee keer verwerken hetzelfde effect heeft als die één keer verwerken. Het gereedschap daarvoor is de webhook-id header.
Elke webhook die AhaSend verstuurt bevat drie security headers, waarvan er één de webhook-id is, een unieke identificatie voor die specifieke gebeurtenis. Cruciaal: wanneer AhaSend een aflevering opnieuw probeert, draagt die retry dezelfde webhook-id als het origineel. Dat geeft je een betrouwbare sleutel om gebeurtenissen te herkennen die je al hebt afgehandeld.
Het patroon komt neer op vier stappen:
webhook_id = request.headers["webhook-id"]
# Sla gebeurtenissen over die je al hebt afgehandeld
if already_processed(webhook_id):
return ok()
process_event(event)
# Leg pas vast na succesvolle verwerking
mark_processed(webhook_id)Het kernidee: voordat je werk doet, controleer je of je deze webhook-id al hebt gezien. Zo ja, bevestig het verzoek met een 200 en doe verder niets. Zo nee, verwerk de gebeurtenis en leg daarna de ID vast, zodat toekomstige retries als duplicaten worden herkend.
Een paar praktische opmerkingen om dit goed te implementeren.
Sla verwerkte ID's op in iets duurzaams, zoals je database of Redis, geen in-memory set die verdwijnt als je service herstart. Een unique constraint in je database op de webhook-ID is een nette manier om dit af te dwingen, want een dubbele insert mislukt simpelweg en dat kun je behandelen als signaal dat je de gebeurtenis al hebt gezien.
Leg de ID pas vast nadat je de gebeurtenis succesvol hebt verwerkt. Leg je hem eerst vast en mislukt de verwerking daarna, dan wordt de retry als duplicaat behandeld en overgeslagen, en ben je de gebeurtenis kwijt.
Reageer snel. Omdat de timeout 10 seconden is, doe je synchroon het minimum, bevestig je de webhook en verplaats je zwaarder werk naar een achtergrondwachtrij. Dat vermindert om te beginnen al het aantal retries dat door timeouts wordt veroorzaakt.
Dit is de industriestandaard
Deze aanpak is niet specifiek voor AhaSend. Zo worden robuuste webhook-integraties overal gebouwd. Stripe documenteert bijvoorbeeld exact hetzelfde patroon: hun events kunnen af en toe meer dan eens binnenkomen, en ze instrueren ontwikkelaars om duplicaten af te handelen met de event-ID. Bouw je je integratie op deze manier, dan is hij niet alleen robuust met AhaSend maar met elke goed ontworpen webhook-provider.
AhaSend volgt de Standard Webhooks-specificatie, dus de headers en verificatieaanpak die hier beschreven worden, sluiten aan op een breed ecosysteem van tools en libraries.
Kort samengevat
Dubbele webhook-afleveringen zijn normaal, verwacht en geen teken dat er iets stuk is. Ze komen voor omdat webhook-aflevering prioriteit geeft aan het nooit verliezen van een gebeurtenis boven het nooit herhalen ervan. Ga ermee om door je verwerking idempotent te maken: controleer de webhook-id header voordat je verwerkt, sla gebeurtenissen over die je al hebt gezien, en leg ID's pas duurzaam vast na succesvolle verwerking.
Deze post behandelt het concept. Voor de volledige implementatiedetails, inclusief de exacte headerstructuur, signatureverificatie, retry-gedrag en eventtypes, ga je naar de webhooks API-documentatie. Dat is de plek om naartoe te gaan als je klaar bent om een productiewaardige integratie te bouwen.