Webhook-handtekening-validator
Je provider stuurt een handtekening-header, je handler zegt dat die niet klopt, en de docs antwoorden met een te installeren SDK. Dat vertelt je niet waarom. Plak hieronder de handtekening, je secret en de ruwe body: we herberekenen de HMAC in je browser en proberen bij falen de gebruikelijke oorzaken — een spatie te veel in het secret, een afsluitende regeleinde, een opnieuw geserialiseerde body, een verlopen timestamp.
Kort gezegd: een webhook-handtekening faalt bijna altijd om één van vijf redenen — verkeerd secret (test vs live), een door je framework opnieuw geserialiseerde body, een timestamp buiten het venster van 5 minuten, een afsluitende regeleinde of een overgebleven prefix sha256= / v0=. Deze tool herberekent de HMAC en zegt welke.
Plak een falende handtekening
De vier handtekening-schema's
Alle vier zijn HMAC, maar ze verschillen in wat er gehasht wordt en hoe het resultaat gecodeerd is. Eén detail fout en elke levering faalt.
| Provider | Wat wordt ondertekend | Algoritme |
|---|---|---|
| Stripe | HMAC van t.body; handtekening en timestamp in één Stripe-Signature-header | SHA-256 hex |
| GitHub | HMAC van de ruwe body; handtekening in X-Hub-Signature-256 | SHA-256 hex |
| Shopify | HMAC van de ruwe body; handtekening in X-Shopify-Hmac-Sha256 | SHA-256 base64 |
| Slack | HMAC van v0:timestamp:body; heeft ook de timestamp-header nodig | SHA-256 hex |
Twilio is de uitzondering — het hasht een canonieke URL plus gesorteerde parameters, niet de body. Gebruik daarvoor de Twilio-handtekening-validator.
De vijf echte oorzaken
Gerangschikt naar hoe vaak ze in support-threads opduiken.
| # | Oorzaak | Symptoom | Oplossing |
|---|---|---|---|
| 1 | Verkeerd secret | Elke levering faalt, zelfs een verse | Test-vs-live-verwarring — providers geven per modus en per endpoint een ander signing-secret. |
| 2 | Opnieuw geserialiseerde body | Faalt nadat je framework de JSON parste | Onderteken de ruwe bytes, niet JSON.stringify(parsed): spatiëring en sleutelvolgorde veranderen de hash. |
| 3 | Verlopen timestamp | Slaagt in tests, faalt bij opnieuw verzonden events | Stripe en Slack weigeren alles ouder dan 5 minuten. Stuur geen uren oud gevangen event opnieuw. |
| 4 | Afsluitende regeleinde | Body één byte scheef, handtekening nooit goed | Een proxy of editor voegde een regeleinde toe. De tool markeert precies dit geval. |
| 5 | Prefix letterlijk vergeleken | sha256= of v0= in de vergelijking meegenomen | Vergelijk alleen het hex/base64-deel, niet de schema-prefix. |
De ruwe-body-valkuil
De belangrijkste oorzaak: je verifieert tegen de geparste-dan-opnieuw-geserialiseerde body in plaats van de exacte bytes. JSON.stringify(JSON.parse(body)) verandert spatiëring en sleutelvolgorde, dus de HMAC klopt niet meer. Lees de ruwe body voordat iets hem parst.
// Express: keep the RAW bytes, do NOT let JSON re-serialise them
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
const raw = req.body; // Buffer, exactly as received
verify(raw, req.headers['stripe-signature'], secret);
});# Flask: request.get_data() returns the raw body; request.json does NOT raw = request.get_data() # bytes, untouched verify(raw, request.headers['X-Hub-Signature-256'], secret)
// Next.js App Router: read the text before parsing
export async function POST(req) {
const raw = await req.text(); // sign THIS, not JSON.stringify(await req.json())
verify(raw, req.headers.get('stripe-signature'), secret);
}Vuistregel: vang de handtekening en de ruwe bytes aan de rand, verifieer, dan pas parsen.
Timestamps en replays
Stripe en Slack verweven een timestamp in de ondertekende string en weigeren alles ouder dan vijf minuten — dat is replay-bescherming, geen bug. GitHub en Shopify niet, dus een geldige GitHub-handtekening blijft voor altijd geldig. Zegt de tool geldig maar verlopen, dan zijn je secret en body prima; je stuurt gewoon een oud event opnieuw. Genereer een verse levering in plaats van een gevangen event opnieuw te sturen.
Zodra de handtekening klopt
Een correct ondertekend testevent naar je eigen endpoint sturen? De webhook-signer bouwt er één voor alle zes providers. Om echte leveringen te vangen en te inspecteren, richt ze op een Relay-tunnel.
Veelgestelde vragen
Bewaren jullie mijn signing-secret?
Nee. De HMAC wordt in je browser berekend met Web Crypto; het secret en de body bereiken onze servers nooit. Open het Netwerk-tabblad: bij klikken op Controleren gaat er geen verzoek uit.
Welke providers worden ondersteund?
Stripe, GitHub, Shopify en Slack — de vier HMAC-over-body-schema's. Twilio ondertekent in plaats daarvan een canonieke URL en heeft dus een eigen tool: de Twilio-handtekening-validator.
Het slaagt hier maar mijn server weigert het toch. Waarom?
Bijna altijd de body. Je framework parste de JSON en je serialiseerde die opnieuw vóór verificatie, wat spatiëring en sleutelvolgorde verandert. Onderteken de ruwe bytes — zie de Express-, Flask- en Next-snippets hierboven.
Wat betekent 'geldig maar verlopen'?
De HMAC klopt, maar Stripe en Slack weigeren ook handtekeningen ouder dan vijf minuten om replays te blokkeren. Als je een event ving en het later opnieuw stuurt, faalt de verificatie op de timestamp ook al klopt het secret.