Lanceringsaanbieding: −50 % op je eerste betaling, tot 30 november.Bekijk de aanbieding →
webhook·toolkit
Twilio

Twilio-handtekeningvalidatie mislukt — ontdek waarom

Laatst bijgewerkt: juli 2026

Je hebt een X-Twilio-Signature-header, je code zegt dat die niet klopt, en de docs van Twilio antwoorden met een SDK om te installeren. Dat vertelt je niet welke van je URL, je params of je token fout is. Plak ze dus alle drie hieronder: we berekenen de handtekening opnieuw voor elke aannemelijke URL-vorm en tonen je welke matcht. De regel die matcht, is je diagnose.

Kort gezegd: een Twilio-handtekening faalt bijna altijd omdat de URL die je code opnieuw opbouwt niet byte voor byte gelijk is aan de URL die Twilio aanriep — meestal http:// in plaats van https:// achter een reverse proxy. Het algoritme is zelden het probleem.

Controleer een handtekening die Twilio afwees

Alles draait in je browser (WebCrypto HMAC-SHA1). Je Auth Token wordt nooit naar onze servers gestuurd, en er verlaat geen enkele request deze pagina zolang je de tool gebruikt. Open het netwerktabblad en controleer het — dat hebben we juist liever.

De exacte string die Twilio ondertekent

Alle andere secties op deze pagina volgen hieruit. Twilio ondertekent niet je body, en ook niet je headers. Het ondertekent één string, die zo wordt opgebouwd:

  1. Begin met de volledige URL die Twilio aanriep: scheme, host, poort als die expliciet was, pad en de querystring.
  2. Sorteer de namen van de POST-parameters in ASCII-volgorde.
  3. Voeg voor elke parameter de naam toe, direct gevolgd door de waarde. Geen scheidingsteken, geen isgelijkteken, geen encoding, niets ertussen.
  4. Bereken de HMAC-SHA1 van de hele string met je Auth Token.
  5. Codeer de digest in base64. Dat is X-Twilio-Signature.

Hetzelfde met echte waarden

URL aangeroepen door Twilio
https://api.example.com/twilio/sms
POST-parameters (form-urlencoded)
AccountSid=AC1234567890abcdef
Body=Hello
From=%2B15017122661
To=%2B15558675310
De ondertekende string — 101 tekens, aan elkaar geplakt
https://api.example.com/twilio/smsAccountSidAC1234567890abcdefBodyHelloFrom+15017122661To+15558675310
Auth Token (een wegwerpexemplaar, voor het voorbeeld)
12345678901234567890123456789012
base64(HMAC-SHA1(string, token))
REsmqJpWLoqe1Q/VJXTZjx4eviY=

Kijk wat er niet in staat. Geen &, geen =, geen percent-encoding: de waarde is +15017122661, niet %2B15017122661. Wie de params zelf opbouwt door de ruwe body te splitsen, vergeet meestal te decoderen en krijgt een handtekening die fout is om een reden die geen enkele foutmelding ooit noemt.

Verander één byte van die 101 tekens en de handtekening verandert volledig. Dat is het hele verhaal: je code en Twilio moeten exact dezelfde string opbouwen, en elke proxy, router en middleware daartussen kan die ongemerkt veranderen.

Vier URL's, vier handtekeningen, één waarheid

Zelfde Auth Token, zelfde vier params als hierboven. Alleen de URL verandert — en dat is genoeg:

URL gebruikt om de string op te bouwenResulterende handtekeningWaar het vandaan komt
https://api.example.com/twilio/smsREsmqJpWLoqe1Q/VJXTZjx4eviY=Wat Twilio ondertekent
http://api.example.com/twilio/smsNpKfrAiY/l6i2mXxfWlrjyRkytY=Wat je app ziet achter SSL-terminatie
https://api.example.com:443/twilio/smsvMbUG7KsMVUK80lzK6a+4IMFPqU=Hoe een handmatig opgebouwde URL eruitziet
https://api.example.com/twilio/sms/8LRHF+mPXykd0Jij9WhgsI7agMs=Wat een router die paden normaliseert je geeft

Niets in de fout vertelt je welke van de vier het is. Dat doet de tool: hij berekent ze allemaal, tot zestien URL-vormen, en vergelijkt elk ervan met de handtekening die Twilio echt stuurde. De groene regel is het antwoord.

De 8 echte oorzaken

Gerangschikt naar hoe vaak ze opduiken op het Twilio-communityforum en in StackOverflow-threads over deze fout. Die volgorde is onze eigen lezing, geen gemeten statistiek — gebruik haar als zoekvolgorde, niet als feit.

#OorzaakHet symptoom dat het verraadtOplossing
1SSL-terminatie (X-Forwarded-Proto)Slaagt met curl op localhost, faalt zodra het achter een load balancer staat. Je log toont http://, de Console toont https://.Vertrouw de proxy: app.set('trust proxy', true), ProxyFix(app.wsgi_app, x_proto=1), TrustedProxies in Laravel.
2Expliciete poort in de opnieuw opgebouwde URLJe log toont https://host:443/… — Twilio schrijft nooit een impliciete poort.Bouw op vanuit de Host- / X-Forwarded-Host-header. Plak hostname en poort niet aan elkaar.
3Querystring weggelaten of opnieuw ge-encodeerdAlleen de URL's met ?params falen. Je framework gaf het routepatroon terug, niet het ruwe target.Gebruik het ruwe request-target (req.originalUrl, request.url) en laat de encoding met rust.
4Params gewijzigd vóór de validatieFaalt alleen bij sommige berichten — die met een leeg veld of witruimte aan het eind.Valideer voordat een middleware de body aanraakt. Python: parse_qs(…, keep_blank_values=True). Laravel: sluit de route uit van TrimStrings.
5JSON-body gevalideerd als formulierdataWebhooks van Conversations en Studio falen, gewone sms-webhooks slagen.Gebruik validateRequestWithBody() en de queryparameter bodySHA256.
6Secundaire Auth Token niet gepromoveerdBegon te falen direct na een tokenrotatie, op 100% van de requests.Console → Account → Auth Token: een secundaire token ondertekent niets tot je hem promoveert tot Primary.
7Herhaalde params (SDK-bug)Alleen groepsberichten en Conversations falen, en niet reproduceerbaar.twilio-node #722: params['MessagingBinding.Address'].sort() vóór validateRequest().
8Proxy-rewrite die het pad verandertFaalt alleen in productie. Twilio roept /webhooks/sms aan, je app ontvangt /sms.Valideer tegen het publieke pad, niet tegen het interne pad dat na de rewrite overblijft.

De bug met herhaalde params — het ligt niet aan jou

Deze verdient een eigen sectie, want je kunt een uur in de docs van Twilio lezen zonder hem ooit te vinden. Groepsberichten en Conversations sturen dezelfde key meerdere keren:

De body die Twilio POST
MessagingBinding.Address=%2B15558675310&MessagingBinding.Address=%2B15017122661

De server van Twilio sorteert de keys en plakt ze daarna aan elkaar. Jouw params-map bevat een array. twilio-node doet data += key + params[key], en JavaScript zet die array om naar een string door de elementen met komma's samen te voegen — in volgorde van binnenkomst. Als je framework de twee adressen in een andere volgorde parste dan de server van Twilio gebruikte, verschillen de twee strings en faalt de validatie. Zelfde token, zelfde URL, zelfde params. Het faalt toch.

Twee volgordes, twee handtekeningen — verder alles identiek
Volgorde van binnenkomst: +15558675310, dan +15017122661LJaNX3wj9/RRc4+GG1kwgPndM1c=
Gesorteerd: +15017122661, dan +15558675310A7jNj66u8x1pDaqy3CNma9Q0XvA=
De workaround
// twilio-node issue #722
// Herhaalde params komen binnen als array; sorteer ze vóór het valideren.
if (Array.isArray(params['MessagingBinding.Address'])) {
  params['MessagingBinding.Address'].sort();
}
const valid = twilio.validateRequest(authToken, signature, url, params);

De tool test beide volgordes automatisch. Wordt de gesorteerde regel groen, dan heb je een bug in de SDK gevonden, niet in je code — en kun je stoppen met je handler steeds opnieuw door te lezen.

Werkt lokaal, breekt in productie

Het meest voorkomende verhaal, en het heeft één oorzaak: in dev geeft je tunnel de request aan je app door met hetzelfde scheme dat Twilio gebruikte. In productie beëindigen Traefik, nginx of Cloudflare de TLS en sturen ze gewone HTTP door op poort 3000. Je framework meldt wat het op zijn socket ziet — http:// — en ondertekent een string die Twilio nooit heeft opgebouwd.

Express
// Achter Traefik / nginx / Cloudflare
app.set('trust proxy', true);
// req.protocol volgt nu X-Forwarded-Proto
const url = req.protocol + '://' + req.get('host') + req.originalUrl;
Flask / Werkzeug
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
# request.url bouwt nu weer https:// op
validator.validate(request.url, request.form, signature)
Laravel
// app/Http/Middleware/TrustProxies.php
protected $proxies = '*';           // alleen als de proxy van jou is
protected $headers = Request::HEADER_X_FORWARDED_ALL;

Eén regel, drie ecosystemen. Zodra de proxy vertrouwd wordt, leest het framework X-Forwarded-Proto en X-Forwarded-Host, bouwt het https:// opnieuw op en matcht de handtekening. Vertrouw alleen proxy's die je echt beheert: met trust proxy op true kan alles upstream elk scheme claimen dat het wil.

JSON-bodies: bodySHA256

Twilio-webhooks zijn standaard application/x-www-form-urlencoded — nooit JSON. Maar Conversations en Studio kunnen JSON sturen, en dan veranderen de regels: de body zit helemaal niet in de params. Twilio hasht hem met SHA-256, voegt die hash als queryparameter bodySHA256 aan de URL toe en ondertekent de URL met een lege parameterlijst.

Een JSON-webhook valideren (Node)
const valid = twilio.validateRequestWithBody(
  process.env.TWILIO_AUTH_TOKEN,
  req.headers['x-twilio-signature'],
  fullUrl,        // moet nog steeds ?bodySHA256=… bevatten
  rawBody         // de ruwe string, NIET eerst JSON.parse en dan weer stringify
);

Hier zitten twee valkuilen. Strip je de querystring van de URL, dan strip je ook bodySHA256, en faalt de controle zonder dat de body ooit genoemd wordt. En als je framework de JSON al parste voordat jij erbij kwam, geeft JSON.stringify() je de oorspronkelijke bytes niet terug — sleutelvolgorde en witruimte zijn weg. Vang de ruwe body af. De JSON-modus in de tool hierboven toont beide hashes naast elkaar, zodat je ziet welke van de twee je te pakken had.

Twilio-feiten om in je achterhoofd te houden

Details die bepalen of de handtekening überhaupt een kans krijgt om gecontroleerd te worden:

  • HMAC-SHA1, niet SHA256. Stripe, GitHub en Shopify gebruiken allemaal SHA-256, dus wie een werkende handler van een van hen kopieert en alleen de headernaam vervangt, krijgt een validator die nooit iets matcht.
  • De payload is standaard application/x-www-form-urlencoded, nooit JSON. Als je framework alleen JSON-bodies parst, is je params-map leeg en faalt elke handtekening.
  • Twilio wacht 15 seconden op een voice-webhook, 5 seconden bij Conversations. Valideer eerst, antwoord, en doe daarna het trage werk — een handtekeningcontrole die na een databasecall draait, kan een time-out krijgen voordat hij terugkomt.
  • Standaard één nieuwe poging, en tot vijf met connection overrides. Een foute handtekening is dus niet één mislukt bericht: het is één bericht dat na een paar pogingen verloren gaat.
  • De Debugger bewaart zijn logs 30 dagen. Daar vind je de URL die Twilio echt aanriep, en dat is precies de input die deze pagina nodig heeft.
  • Bij WebSocket-verbindingen komt de header in kleine letters binnen: x-twilio-signature. Vergelijk headers hoofdletterongevoelig.

En Twilio-fout 57012?

Fout 57012 (ongeldige handtekening) vertelt je dat de handtekening niet overeenkomt met de handtekening die Twilio berekende, en wijst naar de gebruikelijke verdachten: verkeerde token, verkeerde URL, params onderweg gewijzigd, body-hash ontbreekt. Allemaal waar. Maar het stopt precies waar het debuggen begint, want het kan je niet vertellen welke daarvan op jouw request van toepassing is. Let er ook op dat vanuit Twilio gezien de oorzaken 1, 2, 3, 7 en 8 hierboven allemaal gewoon “verkeerde URL” zijn — vijf verschillende bugs, vijf verschillende fixes, één foutcode. Die uit elkaar halen is het doel van deze pagina.

Wanneer gebruik je wat — eerlijk gezegd

We zijn hier niet het antwoord op alles, en doen alsof zou je middag verspillen.

De docs van Twilio

Je schrijft de handler vanaf nul en wilt het referentie-algoritme en de SDK voor je taal. Het is de bron van waarheid — webhooks-security dekt zeven talen. Er is helemaal geen troubleshooting-sectie, en daarom ben je hier beland.

Hookdeck

Je probleem is niet één kapotte request, maar de aflevering: je wilt retries die je zelf beheert, een dead letter queue, time-outs langer dan de 15 seconden van Twilio, omzetting van form naar JSON, of handtekeningverificatie aan de edge zodat je app nooit een niet-geverifieerde request ziet. Dat is een platform, en een echt platform. Betaal ervoor.

Deze pagina

Eén handtekening, nu meteen, en je wilt weten welke regel van je code liegt. Dertig seconden, niets te installeren, geen account. Daarna ga je de proxyconfig fixen en vergeet je dat we bestaan. Prima uitkomst.

Als webhooks een vast onderdeel van je stack zijn in plaats van een brand die je aan het blussen bent, stuurt onze Relay-tunnel echt Twilio-verkeer door naar localhost, en speelt de Twilio-webhooktester ondertekende requests af op je handler, zodat je de fix kunt checken zonder een echte sms te verbruiken.

Twilio webhook testerRelay

Veelgestelde vragen

Waarom faalt mijn Twilio-handtekening achter ngrok of een proxy?

Omdat de proxy de TLS beëindigt en gewone HTTP doorstuurt naar je app. Twilio ondertekende de https://-URL; je framework bouwt http:// op vanuit zijn eigen socket. De strings verschillen, dus de HMAC verschilt. Vertrouw de proxy zodat X-Forwarded-Proto wordt gebruikt om de URL op te bouwen, en hij matcht weer.

Moet de poort in de URL staan?

Alleen als Twilio hem aanriep. Twilio ondertekent de URL zoals geconfigureerd, dus een standaardpoort 443 komt nooit in de string voor. Voeg je zelf :443 toe bij het opbouwen, dan krijg je een andere handtekening. Bouw de host op vanuit de Host-header in plaats van hostname en poort aan elkaar te plakken.

Hoe valideer ik een JSON-body van Twilio?

Met validateRequestWithBody() in plaats van validateRequest(). Twilio hasht de body met SHA-256, zet de hash in een queryparameter bodySHA256 en ondertekent de URL zonder params. Je hebt de ruwe bytes van de body nodig en de volledige URL inclusief die queryparameter.

Waarom werkt het lokaal maar niet in productie?

In dev geeft je tunnel je app meestal hetzelfde scheme dat Twilio gebruikte. In productie krijgt je app van Traefik, nginx of Cloudflare gewone HTTP op een interne poort. Zelfde code, andere opnieuw opgebouwde URL, dus de handtekening matcht niet meer. Het is de nummer-één-oorzaak op deze pagina.

Wordt mijn Auth Token naar jullie servers gestuurd?

Nee. De HMAC draait in je browser met WebCrypto, en de tool verstuurt geen enkele request zolang je hem gebruikt. Open je netwerktabblad en kijk mee. Je Auth Token is een productiesecret — dit zou je moeten vragen aan elke site die erom vraagt, ook aan de onze.

Is de Twilio-handtekening SHA-256, zoals die van Stripe?

Nee, Twilio gebruikt HMAC-SHA1 en codeert die in base64. Stripe, GitHub en Shopify gebruiken SHA-256. Een werkende Stripe-handler aanpassen door de header te hernoemen levert een validator op die alles weigert, en dat is een veelvoorkomende manier om bij deze fout uit te komen.

Lanceringsaanbieding · −50 %

Ben je deze week een webhook-integratie aan het debuggen?

De 7-dagen-Pass ontgrendelt AI-uitleg en kant-en-klare handlercode, 25 permanente URL's, 30 dagen geschiedenis en de Relay-tunnel naar localhost. Eén keer betalen, geen abonnement.

€ 5 € 2,50 eenmalig
Eenmalige betaling · geen abonnement · 14 dagen geld terug · lanceringsprijs tot 30 november
Twilio-handtekeningvalidatie mislukt: vind de oorzaak · Webhook Toolkit