Een koppeling die alleen werkt als alles goed gaat, werkt niet. In de praktijk komt hetzelfde bericht twee keer binnen, geeft de andere kant een 429 terug, verloopt een token op zondagavond en ligt er een systeem zes uur uit. Hieronder de zes oorzaken daarachter, met per oorzaak het symptoom, wat er technisch gebeurt en hoe je controleert of het bij jou is afgevangen.
Het korte antwoord: koppelingen falen zelden aan de code die de gelukkige route afhandelt. Ze falen aan de randgevallen eromheen — herhaling, wachttijd, verlopen toegang, uitval aan de andere kant, en onaangekondigde wijzigingen. Al die gevallen zijn te voorzien; ze zijn alleen niet gratis.
Het faalpad, in woorden
Vrijwel elk diagram dat je online vindt, tekent de gelukkige route: order binnen, order geboekt, klaar. Het pad dat je nodig hebt is het andere. Zo ziet het eruit, met de uitzonderingstakken erbij.
- Order binnen. Een webhook of pollende taak levert een bericht aan. Het krijgt een uniek event-id en wordt weggeschreven vóór verwerking, zodat ontvangen en verwerken niet samen falen.
- Validatie. Klopt de structuur, staat het artikel in beide systemen, is de btw-code bekend? Zo niet, dan gaat het bericht naar de uitzonderingenbak — daar hoort een mens naar te kijken, geen retry.
- API-call. Schrijven naar het doelsysteem, met een idempotency key zodat herhaling geen tweede boeking oplevert.
- 429 of 5xx. De andere kant zegt "te druk" of "hier ging iets mis". Hier gaan de meeste koppelingen fout: ze proberen het meteen opnieuw, of helemaal niet.
- Wachtrij en backoff. Terug de rij in met oplopende wachttijd — 1, 2 en 4 seconden, met willekeur erin (backoff met jitter volgens de Hookdeck-gids, geraadpleegd 9 september 2026).
- Derde poging faalt. Zonder grens blijft een kapot bericht ronddraaien en houdt het de rest tegen.
- Dead-letter queue. Naar een aparte bak, mét payload, foutmelding, aantal pogingen en tijdstip.
- Melding. Een DLQ waar niemand naar kijkt is een prullenbak; de melding gaat naar een genoemde ontvanger.
- Mens beslist. Corrigeren en opnieuw aanbieden, handmatig boeken, of laten vervallen met een notitie. Daarna replay in batches.
De zes oorzaken op een rij
| Oorzaak | Wat je merkt | Wat er technisch gebeurt | Hoe je het controleert |
|---|---|---|---|
| Geen idempotentie | Dubbele orders, facturen en klanten | Een POST wordt herhaald na een timeout; de andere kant maakt een tweede record | Gaat er een idempotency key mee, en wordt er op event-id gededupliceerd? |
| Retries zonder plan | Pieken van duplicaten, of berichten die na één poging verdwijnen | Blind herhalen zonder wachttijd, of niet herhalen bij een tijdelijke fout | Welke statuscodes worden herhaald, met welke wachttijd, en wanneer stopt het? |
| Rate limits | Trage verwerking of ontbrekende orders op drukke dagen | De andere kant geeft HTTP 429; ongebufferde calls lopen tegen een muur | Zit er een wachtrij vóór de API en wordt de limietstatus uitgelezen? |
| Verlopen of ingetrokken tokens | De koppeling stopt plotseling volledig, vaak in een weekend | OAuth-refresh mislukt of de autorisatie is ingetrokken: 401 of 403 | Wie beheert de credentials, en is er een melding vóór het verloopt? |
| Het andere systeem ligt eruit | Ontbrekende uren, halve orders, voorraad die niet klopt | Calls falen tijdens de storing; zonder wachtrij zijn die berichten weg | Wat gebeurt er met events tijdens uitval, en hoe worden ze ingehaald? |
| Het andere systeem verandert | Een veld is leeg, een import kapt af, een PDF wordt niet herkend | Veldnaam, API-versie of layout gewijzigd; je leest iets anders dan verwacht | Draaien er contracttests, en monitor je op onverwacht lege velden? |
Oorzaak 1: dezelfde order komt twee keer binnen
Begin bij het symptoom: twee identieke facturen in de boekhouding. Dat komt bijna nooit doordat de klant twee keer bestelde, maar doordat het verzoek is herhaald. GET, PUT, HEAD, OPTIONS, TRACE en DELETE zijn idempotent; POST en PATCH zijn dat niet gegarandeerd (volgens MDN, geraadpleegd 9 september 2026). En een order aanmaken is een POST.
Het scenario is standaard: je stuurt de order, de andere kant boekt hem, de verbinding valt weg vóór het antwoord terugkomt. Probeer je het opnieuw, dan heb je twee facturen; doe je niets, dan mis je er misschien één. Een idempotency key lost dat op: dezelfde sleutel bij elke poging, en de andere kant geeft bij herhaling het opgeslagen resultaat terug.
Stripe documenteert dat model concreet: sleutels tot 255 tekens, een V4-UUID als aanbeveling, het opgeslagen antwoord komt terug inclusief eerdere 500-fouten, sleutels mogen na minimaal 24 uur worden opgeruimd, en het werkt alleen op POST (volgens de Stripe-documentatie, geraadpleegd 9 september 2026). Ondersteunt het doelsysteem het niet, bouw het dan zelf: houd per event-id bij of het verwerkt is, minstens zo lang als je retry-venster.
Oorzaak 2: opnieuw proberen zonder plan
"Gewoon opnieuw proberen" is de meest voorkomende reparatie en een grote veroorzaker van schade. Herhalen mag alleen bij fouten die vanzelf overgaan, en alleen met een oplopende wachttijd.
| Statuscode | Herhalen? | Wachtstrategie | Wat het meestal betekent |
|---|---|---|---|
| 400 / 422 | Nee | Direct naar de uitzonderingenbak | Het bericht klopt niet; opnieuw sturen verandert niets |
| 401 / 403 | Niet blind | Eén keer token vernieuwen, daarna melden | Toegangsprobleem, geen storing: beheerwerk |
| 404 | Meestal niet | Naar de uitzonderingenbak | Doelobject bestaat niet; vaak een mappingfout |
| 409 | Soms | Opnieuw ophalen, dan één poging | Conflict: iemand wijzigde hetzelfde record |
| 429 | Ja | Wachttijd uit de respons, anders oplopende backoff | Rate limit bereikt |
| 500 / 502 / 503 / 504 | Ja | Oplopende backoff met jitter en een maximum | Tijdelijk probleem aan de andere kant |
| Timeout zonder antwoord | Ja, mits idempotent | Zelfde als 5xx, met idempotency key | Onbekende uitkomst: het kan geboekt zijn |
Exponential backoff betekent dat de wachttijd verdubbelt: 1 seconde, dan 2, dan 4. Jitter is de willekeurige marge erbovenop, zodat duizend berichten die tegelijk faalden niet tegelijk terugkomen (backoff met jitter volgens de Hookdeck-gids, geraadpleegd 9 september 2026). En je stopt: na een vast aantal pogingen gaat het bericht naar de dead-letter queue. Eindeloos herhalen is geen veerkracht maar een verstopte rij.
Oorzaak 3: rate limits
Een rate limit is de afspraak hoeveel je per tijdseenheid mag vragen. Shopify hanteert voor de GraphQL Admin API een puntenmodel, geeft HTTP 429 bij overschrijding en levert een throttleStatus terug met de maximaal beschikbare punten, de nu beschikbare punten en het hersteltempo; de aanbeveling is één seconde te wachten (volgens Shopify, geraadpleegd 9 september 2026). Je ziet de limiet dus aankomen.
De oplossing is architectureel: zet een wachtrij vóór de API in plaats van een muur ertegenaan. Berichten komen binnen zo snel als ze willen, de verwerker haalt ze eruit in het tempo dat de andere kant aankan. Bij een actieweek loopt de rij tijdelijk op — zichtbaar in je monitoring — in plaats van dat orders sneuvelen.
Oorzaak 4: verlopen of ingetrokken tokens
401 en 403 zijn geen storing maar een beheerprobleem, en ze leggen alles tegelijk stil. Een verlopen refresh-token, een medewerker die uit dienst gaat en wiens account de autorisatie droeg, een ingetrokken app-toestemming: het effect is hetzelfde. De koppeling doet niets meer, en zonder melding merk je het pas als iemand een factuur mist.
- Koppel op een serviceaccount, niet op het persoonlijke account van een medewerker.
- Leg vast wie de credentials beheert en waar ze staan — niet in een chatbericht, niet in de code.
- Zet een melding op de vervaldatum van tokens en certificaten, niet pas als ze verlopen zijn.
- Behandel een 401 als aparte categorie in je alerting: dit vraagt een mens, geen retry.
Oorzaak 5: het andere systeem ligt eruit
Onderhoud, een storing of een netwerkprobleem: een paar uur geen bereikbaarheid is normaal. De vraag is wat er in die uren met je berichten gebeurt. Zonder wachtrij zijn ze weg — of de webhook herhaald wordt, hangt van de verzender af. Met een wachtrij lopen ze op en worden ze verwerkt zodra de andere kant terug is.
Gevaarlijker is gedeeltelijke uitval: de andere kant antwoordt wel, maar half. Dan krijg je halve orders — een order zonder regels, een factuur zonder klant. Een schrijfactie van meerdere stappen hoort daarom één eindpunt te hebben: pas als de laatste stap bevestigd is, geldt het bericht als verwerkt.
Herstel na downtime
- Bepaal het venster. Van wanneer tot wanneer ging het mis, en welke bron is leidend.
- Repareer eerst de oorzaak. Replayen naar een systeem dat nog weigert, vult je DLQ opnieuw.
- Valideer een steekproef. Bied een handvol berichten aan en controleer de uitkomst.
- Replay in batches. Anders loop je direct tegen de rate limit aan.
- Controleer op duplicaten. Replay zonder idempotentie verdubbelt het probleem. Daarom is idempotentie geen luxe.
- Documenteer het. Welk venster, hoeveel berichten, wat handmatig is afgehandeld, wat er structureel is aangepast.
Oorzaak 6: het andere systeem verandert
Een veld krijgt een andere naam, een API-versie wordt uitgefaseerd, een leverancier past de layout van zijn factuur-PDF aan. Dit is de sluipende variant: geen foutmelding, wel een verkeerd resultaat. Een bedrag dat leeg blijft, een btw-code die op de standaardwaarde valt, een klantnummer dat niet meer matcht.
Contracttests zijn het antwoord: een kleine set tests die periodiek controleert of de andere kant nog levert wat je verwacht — velden, types, waarden die niet leeg mogen zijn. Ze draaien los van je verwerking en slaan alarm vóór de eerste verkeerde boeking. Monitor daarnaast op onverwachte leegte: een veld dat normaal altijd gevuld is en ineens vaak leeg is, is een signaal zonder foutcode.
Loop de zes oorzaken langs voor je eigen koppeling en noteer of je het antwoord kent. Kom je er bij drie of meer niet uit, stuur dan de foutmelding die je nu ziet plus de twee systeemnamen — dan zeggen we welk mechanisme eronder zit.
De dead-letter queue: waar mislukte berichten heen gaan
Een dead-letter queue is een aparte bak voor berichten die het definitief niet haalden. Geen foutmelding maar een bewaarplaats: payload, laatste foutmelding, aantal pogingen, tijdstip. Daarmee kun je het bericht later opnieuw aanbieden zonder dat de bron het opnieuw stuurt. Brixxs beschrijft hetzelfde principe als foutwachtrij met bron, doel en eigenaar per bericht (geen datum op de pagina).
Wat je ermee doet is het punt. Een DLQ die alleen vult, is een langzame manier om data te verliezen. De routine: dagelijks kijken wat erin staat, per bericht bepalen of het een datafout is (naar de mens) of een systeemfout (repareren en replayen), en de bak leeg opleveren. De Hookdeck-gids beschrijft dezelfde volgorde: onderzoeken, repareren, valideren, in batches replayen, monitoren, documenteren (geraadpleegd 9 september 2026).
Monitoring, alerting en de vraag wie er gebeld wordt
| Wat je meet | Waarom | Wie krijgt de melding |
|---|---|---|
| Foutratio per tijdvenster | Eén fout is ruis; een stijgende ratio is een storing | Beheerder |
| Wachtrijdiepte | Loopt de rij op, dan verwerk je langzamer dan er binnenkomt | Beheerder |
| Ouderdom oudste bericht | Eerlijkste maat voor achterstand: zes uur oud is al fout | Beheerder |
| Berichten in de DLQ | Alles hierin is data die nu niet verwerkt is | Operationeel verantwoordelijke |
| Verwerkingstijd | Loopt vooruit op rate limits en timeouts | Beheerder |
| Uitblijvende instroom | Nul berichten kan betekenen dat de bron gestopt is | Beheerder |
Een melding die niemand leest is geen melding. Leg drie dingen vast: welke drempel de melding veroorzaakt, wie hem ontvangt, en binnen welke tijd er gereageerd wordt — dat laatste hangt samen met de reactietijden in het onderhoudsniveau om de koppeling heen.
Human-in-the-loop: wat je niet automatiseert
Drie categorieën horen bij een mens: alles wat geld verplaatst buiten de normale route (creditnota's, correcties), alles waar de data ambigu is (een order zonder herkenbaar artikel, een klant die twee keer bestaat), en alles wat één keer voorkomt. Bouw daarvoor geen regel maar een uitzonderingenscherm met de payload, de reden en twee knoppen — dezelfde gedachte als het goedkeuringsmoment in de automatiseringsstromen die wij bouwen.
Wat dit betekent voor de prijs van je koppeling
Alles op deze pagina — idempotentie, retrybeleid, wachtrij, dead-letter queue, alerting met ontvanger, contracttests, herstelprocedure — is bouwwerk. Precies het bouwwerk dat het verschil verklaart tussen twee offertes die allebei "koppeling tussen webshop en boekhouding" heten; zie wat foutafhandeling met de prijs doet. Achteraf inbouwen kan en is duurder: je moet dan ook de bestaande verwerking idempotent maken en de historie opruimen.
Vijf vragen aan je huidige bouwer
- Gaat er een idempotency key mee bij orders en facturen, en wordt er op event-id gededupliceerd?
- Welke statuscodes worden herhaald, met welke wachttijden, en na hoeveel pogingen stopt het?
- Waar landt een definitief mislukt bericht, wat staat er dan in, en hoe bied ik het opnieuw aan?
- Welke metrics worden bewaakt, bij welke drempel gaat er een melding uit, naar wie, en binnen welke reactietijd?
- Wat is de procedure als het andere systeem een paar uur uit ligt, en hoe voorkomt de inhaalslag dubbele boekingen?
Krijg je op drie of meer van deze vragen geen concreet antwoord, dan is er geen foutafhandeling — dan is er een koppeling die het meestal doet. Dat is een ander product, en het verklaart de klant die belt over een order die er niet is.
