API-wijzigingen

Webhook-changelogs: de breaking change die niemand vroeg

5 min lezen

Een REST API-changelog bestaat omdat een aanroeper een respons die hij niet begrijpt kan afwijzen, of op zijn minst een fout luid genoeg logt dat iemand het merkt. Een webhook-ontvanger doet zelden een van beide. Hij krijgt een POST, leest de velden die hij verwacht, en als een veld is verplaatst, van type is veranderd of verdwenen is, crasht het endpoint stilletjes in een achtergrondtaak die niemand in de gaten houdt, of, erger, blijft het draaien met een verkeerde waarde die het nooit heeft gevalideerd. Wat is een breaking change behandelt de algemene definitie; een webhook-payload heeft zijn eigen antwoord nodig, omdat het faalpatroon anders is dan bij een endpoint dat iemand doelbewust aanroept.

Waarom breekt een webhook-payloadwijziging anders dan een wijziging in een API-respons?

Omdat de richting van het verzoek omgekeerd is. Een REST-aanroeper initieert de aanroep en kan een versieheader toevoegen, opnieuw proberen bij een 4xx, of een deprecation-melding in de respons lezen. Een webhook-ontvanger heeft niets daarvan geïnitieerd: jullie server besloot te versturen, besloot wanneer, en besloot welke vorm de body zou hebben. De enige hendel van de ontvanger is de validatie die hij schreef toen de integratie werd gebouwd, en de meeste integraties worden één keer gebouwd, werken, en worden nooit meer herzien totdat ze breken. Die asymmetrie is de hele reden waarom een webhook-payloadwijziging meer voorzichtigheid verdient dan dezelfde wijziging in een responsbody die een aanroeper actief heeft aangevraagd.

Wat telt echt als breaking change in een webhook-payload?

WijzigingBreaking voor de meeste ontvangers
Een nieuw veld toevoegenNee, als ontvangers onbekende velden negeren (verifieer deze aanname, neem hem niet aan)
Een veld verwijderenJa, als iets het leest
Een veld hernoemenJa, functioneel identiek aan het oude verwijderen
Het type van een veld wijzigen (string naar object)Ja, bijna altijd
Velden in de JSON-body herordenenNee, voor elke ontvanger die op sleutel parset, wat ze allemaal zouden moeten zijn
De event-naam of -type wijzigenJa, als ontvangers erop filteren of routeren

De regel “een veld toevoegen is veilig” is degene waar teams het meest op leunen en degene die het meest waard is om te verifiëren in plaats van aan te nemen. Een permissieve JSON-parser negeert onbekende velden standaard, maar een ontvanger die deserialiseert naar een strikt schema, verschillende getypeerde talen doen dit zonder extra configuratie, kan de hele payload afwijzen zodra een onverwacht veld verschijnt. Een veld toevoegen is voor jullie webhook alleen veilig als je weet hoe ontvangers parsen, niet omdat JSON zelf permissief is.

Hoe versieer je een webhook-payload?

Grotendeels zoals bij een API-respons, met één nuance: de ontvanger stuurt nooit een verzoek, dus kan hij niet om een versie vragen, en moet de afzender die vermelden. Dat kan in de body of in een request-header op de levering zelf; de leveringen van GitHub bevatten X-GitHub-Event en X-GitHub-Hook-ID, en de Standard Webhooks-specificatie zet haar metadata in webhook-*-headers. Een versieveld in de payload ("payload_version": 2) is de goedkoopste optie en werkt wanneer ontvangers bereid zijn erop te vertakken. Een geversioneerd event-type (invoice.updated wordt invoice.updated.v2 als een apart event waarop een ontvanger vrijwillig intekent) kost meer werk om te bouwen maar betekent dat de oude vorm blijft stromen naar wie nooit migreerde, wat hier meer telt dan bij een REST-endpoint omdat je niet elke ontvanger kunt bellen om te vragen te updaten. Een instelling per abonnement, gekozen bij registratie van het webhook-endpoint, neemt de beslissing vooraf in plaats van te vertakken bij elke levering, en is de juiste keuze wanneer je al een abonnementsrecord hebt om hem aan te hangen.

POST /ontvanger-endpoint
{
  "event": "invoice.updated",
  "payload_version": 2,
  "data": { "invoice_id": "inv_123", "status": "paid" }
}

Hoe weet je überhaupt wie luistert?

Erger dan de equivalente versie van dit probleem in een API-changelog, omdat een webhook geen inkomend verzoeklog aan jullie kant heeft dat de aanroeper noemt; je hebt alleen jullie eigen uitgaande leveringslog, die vertelt dat een endpoint een 200 kreeg, niet wat het met de body deed. Volg minstens twee dingen: elk geregistreerd endpoint met een eigenaar, dezelfde discipline die interne API-changelogs aanbevelen voor interne consumenten, en jullie leveringsfoutpercentage per endpoint na een payloadwijziging. Een piek in 4xx- of 5xx-responses van een endpoint vlak na een wijziging is het dichtst bij een stack trace dat je krijgt, en vaak het enige signaal dat een ontvanger kapot is, omdat het team dat hem beheert het dagenlang niet merkt.

Moet een webhook-changelog los staan van de API-changelog?

Een aparte sectie op dezelfde pagina, geen aparte publicatie. Een API-changelog legt al vast wie hem leest en hoe erop geabonneerd wordt; een webhook-payloadwijziging hoort in dezelfde feed, duidelijk genoeg gelabeld dat een ontwikkelaar aan ontvangerskant die scant op “raakt dit mijn integratie” erop kan filteren, omdat een webhook-consument vaak geen andere reden heeft om een algemene API-changelog te checken en hem alleen vindt als iemand haar er direct naartoe linkt.

Hoe ziet een redelijk deprecation-venster eruit voor een webhook-payload?

Langer dan de equivalente REST-deprecatie, omdat migratie aan ontvangerskant meestal betekent dat een tweede team, waarmee je misschien geen directe lijn hebt, het moet opmerken, plannen en uitleveren zonder eigen urgentie. Een maand is een redelijke ondergrens voor een veld dat de ontvanger waarschijnlijk nog parset met een permissieve library; drie maanden of meer is veiliger voor het verwijderen van een veld dat een strikt schema volledig zou afwijzen. Stuur de oude en nieuwe vorm samen tijdens het venster wanneer haalbaar (het oude status-veld en zijn vervanger uit versie 2 in dezelfde payload), omdat een ontvanger die het oude veld leest blijft werken zonder zijn code aan te raken, en een die al gemigreerd is het veld dat hij niet meer nodig heeft gewoon negeert.

FAQ

Moeten webhook-consumenten een payloadwijziging bevestigen voordat hij live gaat? Er bestaat standaard geen bevestigingsmechanisme, en precies daarom telt het deprecation-venster hier meer dan bij een REST-API: niemand bevestigt klaar te zijn, dus het venster moet lang genoeg zijn dat de meeste ontvangers op hun eigen tempo migreren voordat de oude vorm verdwijnt.

Is het ooit veilig om onbekende velden zonder kennisgeving toe te voegen? Alleen zodra je hebt geverifieerd, niet aangenomen, dat jullie ontvangers permissief parsen. Een changelog-item kost weinig en haalt het giswerk weg; stilletjes velden toevoegen op de aanname dat “JSON-parsers extra’s negeren” breekt elke ontvanger met strikte deserialisatie.

Wat is de snelste manier om een kapotte webhook-ontvanger te detecteren na een payloadwijziging? Een leveringsfoutpercentage per endpoint, geobserveerd in de uren vlak na de wijziging. Het vertelt je niet wat er kapot is, alleen dat er iets kapot is, maar het is het vroegste en vaak het enige signaal dat je krijgt.

Helpt retry-logica ontvangers om een payloadwijziging te overleven? Nee. Een retry stuurt dezelfde nieuwe payload opnieuw; hij keert niet terug naar een vorm die de ontvanger kan parsen. Een payloadwijziging breekt een ontvanger bij de eerste levering en elke volgende retry identiek.


De technische beweringen in dit artikel zijn niet onafhankelijk gecontroleerd. Klopt er iets niet, laat het ons weten, dan corrigeren we het.

Meer op changeloop: Documentatie voor ontwikkelaars, Changelog-tools vergeleken

changeloop
Het team achter een changelog die de cirkel rondmaakt. Je gebruikers vragen iets, je team levert het, degene die het vroeg hoort ervan.