Offerta di lancio: −50 % sul tuo primo pagamento, fino al 30 novembre.Vedi l'offerta →
webhook·toolkit
Twilio

Validazione della firma Twilio fallita — scopri perché

Ultimo aggiornamento: luglio 2026

Hai un header X-Twilio-Signature, il tuo codice dice che non corrisponde, e la documentazione di Twilio risponde con un SDK da installare. Questo non ti dice quale tra URL, parametri e token è sbagliato. Allora incollali tutti e tre qui sotto: ricalcoliamo la firma per ogni forma plausibile dell'URL e ti mostriamo quella che corrisponde. La riga che corrisponde è la tua diagnosi.

In breve: una firma Twilio fallisce quasi sempre perché l'URL che il tuo codice ricostruisce non è identico byte per byte a quello chiamato da Twilio — di solito http:// al posto di https:// dietro un reverse proxy. L'algoritmo è raramente il problema.

Verifica una firma rifiutata da Twilio

Tutto gira nel tuo browser (WebCrypto HMAC-SHA1). Il tuo Auth Token non viene mai inviato ai nostri server e nessuna richiesta lascia questa pagina mentre usi lo strumento. Apri la scheda Rete e controlla — anzi, preferiamo che tu lo faccia.

La stringa esatta che Twilio firma

Tutto il resto di questa pagina discende da qui. Twilio non firma il tuo body né i tuoi header. Firma una sola stringa, costruita così:

  1. Parti dall'URL completo chiamato da Twilio: schema, host, porta se era esplicita, path e query string.
  2. Ordina i nomi dei parametri POST in ordine ASCII.
  3. Per ciascuno, aggiungi il nome seguito subito dal suo valore. Nessun separatore, nessun segno di uguale, nessuna codifica, niente in mezzo.
  4. Calcola l'HMAC-SHA1 dell'intera stringa con il tuo Auth Token.
  5. Codifica il digest in Base64. Quello è X-Twilio-Signature.

La stessa cosa con valori reali

URL chiamato da Twilio
https://api.example.com/twilio/sms
Parametri POST (form-urlencoded)
AccountSid=AC1234567890abcdef
Body=Hello
From=%2B15017122661
To=%2B15558675310
La stringa firmata — 101 caratteri, concatenati
https://api.example.com/twilio/smsAccountSidAC1234567890abcdefBodyHelloFrom+15017122661To+15558675310
Auth Token (usa e getta, per l'esempio)
12345678901234567890123456789012
base64(HMAC-SHA1(string, token))
REsmqJpWLoqe1Q/VJXTZjx4eviY=

Guarda cosa non c'è. Nessun &, nessun =, nessun percent-encoding: il valore è +15017122661, non %2B15017122661. Chi ricostruisce i parametri spezzando da solo il body grezzo di solito dimentica di decodificare, e ottiene una firma sbagliata per un motivo che nessun messaggio di errore menzionerà mai.

Cambia un solo byte di quei 101 caratteri e la firma cambia completamente. La storia è tutta qui: il tuo codice e Twilio devono costruire esattamente la stessa stringa, e ogni proxy, router e middleware in mezzo è libero di modificarla senza avvisarti.

Quattro URL, quattro firme, una sola verità

Stesso Auth Token, stessi quattro parametri di sopra. Cambia solo l'URL — e basta questo:

URL usato per costruire la stringaFirma risultanteDa dove viene
https://api.example.com/twilio/smsREsmqJpWLoqe1Q/VJXTZjx4eviY=Ciò che Twilio firma
http://api.example.com/twilio/smsNpKfrAiY/l6i2mXxfWlrjyRkytY=Ciò che vede la tua app dietro la terminazione SSL
https://api.example.com:443/twilio/smsvMbUG7KsMVUK80lzK6a+4IMFPqU=L'aspetto di un URL costruito a mano
https://api.example.com/twilio/sms/8LRHF+mPXykd0Jij9WhgsI7agMs=Ciò che ti restituisce un router che normalizza i path

Nulla nell'errore ti dice in quale dei quattro casi ti trovi. È questo che fa lo strumento: li calcola tutti, fino a sedici forme di URL, e confronta ciascuna con la firma che Twilio ha davvero inviato. La riga verde è la risposta.

Le 8 vere cause

Ordinate in base a quanto spesso compaiono nel forum della community Twilio e nei thread StackOverflow su questo errore. Quest'ordine è una nostra lettura, non una statistica misurata — usalo come ordine di ricerca, non come un dato di fatto.

#CausaIl sintomo che la tradisceSoluzione
1Terminazione SSL (X-Forwarded-Proto)Funziona con curl su localhost, fallisce appena sta dietro un load balancer. Il tuo log mostra http://, la Console mostra https://.Rendi attendibile il proxy: app.set('trust proxy', true), ProxyFix(app.wsgi_app, x_proto=1), TrustedProxies su Laravel.
2Porta esplicita nell'URL ricostruitoIl tuo log mostra https://host:443/… — Twilio non scrive mai una porta implicita.Costruiscilo dall'header Host / X-Forwarded-Host. Non concatenare hostname e porta.
3Query string persa o ricodificataFalliscono solo gli URL con ?parametri. Il tuo framework ha restituito il pattern della route, non il target grezzo.Usa il target grezzo della richiesta (req.originalUrl, request.url) e lascia stare la codifica.
4Parametri alterati prima della validazioneFallisce solo su alcuni messaggi — quelli con un campo vuoto o spazi finali.Valida prima che qualsiasi middleware tocchi il body. Python: parse_qs(…, keep_blank_values=True). Laravel: escludi la route da TrimStrings.
5Body JSON validato come form dataFalliscono i webhook di Conversations e Studio, passano quelli SMS semplici.Usa validateRequestWithBody() e il parametro di query bodySHA256.
6Auth Token secondario non promossoHa iniziato a fallire subito dopo una rotazione del token, sul 100% delle richieste.Console → Account → Auth Token: un token secondario non firma nulla finché non lo promuovi a Primary.
7Parametri ripetuti (bug dell'SDK)Falliscono solo i messaggi di gruppo e Conversations, e non in modo riproducibile.twilio-node #722: params['MessagingBinding.Address'].sort() prima di validateRequest().
8Rewrite del proxy che cambia il pathFallisce solo in produzione. Twilio chiama /webhooks/sms, la tua app riceve /sms.Valida rispetto al path pubblico, non a quello interno rimasto dopo il rewrite.

Il bug dei parametri ripetuti — non è colpa tua

Questo merita una sezione a sé, perché puoi leggere la documentazione di Twilio per un'ora senza mai trovarlo. I messaggi di gruppo e Conversations inviano la stessa chiave più volte:

Il body che Twilio invia in POST
MessagingBinding.Address=%2B15558675310&MessagingBinding.Address=%2B15017122661

Il server di Twilio ordina le chiavi, poi concatena. La tua mappa di parametri contiene un array. twilio-node fa data += key + params[key], e JavaScript converte quell'array in stringa unendo i valori con virgole — nell'ordine di arrivo. Se il tuo framework ha parsato i due indirizzi in un ordine diverso da quello usato dal server di Twilio, le due stringhe differiscono e la validazione fallisce. Stesso token, stesso URL, stessi parametri. Fallisce comunque.

Due ordini, due firme — tutto il resto identico
Ordine di arrivo: +15558675310 poi +15017122661LJaNX3wj9/RRc4+GG1kwgPndM1c=
Ordinati: +15017122661 poi +15558675310A7jNj66u8x1pDaqy3CNma9Q0XvA=
Il workaround
// twilio-node issue #722
// I parametri ripetuti ti arrivano come array: ordinali prima di validare.
if (Array.isArray(params['MessagingBinding.Address'])) {
  params['MessagingBinding.Address'].sort();
}
const valid = twilio.validateRequest(authToken, signature, url, params);

Lo strumento testa entrambi gli ordini in automatico. Se la riga ordinata risulta verde, hai trovato un bug nell'SDK, non nel tuo codice — e puoi smettere di rileggere il tuo handler.

Funziona in locale, si rompe in produzione

La storia più comune in assoluto, e ha una sola causa: in sviluppo il tuo tunnel passa la richiesta alla tua app con lo stesso schema usato da Twilio. In produzione, Traefik, nginx o Cloudflare terminano il TLS e inoltrano HTTP semplice sulla porta 3000. Il tuo framework riporta ciò che vede sul suo socket — http:// — e firma una stringa che Twilio non ha mai costruito.

Express
// Dietro Traefik / nginx / Cloudflare
app.set('trust proxy', true);
// req.protocol ora segue 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 ora ricostruisce https://
validator.validate(request.url, request.form, signature)
Laravel
// app/Http/Middleware/TrustProxies.php
protected $proxies = '*';           // solo se il proxy è tuo
protected $headers = Request::HEADER_X_FORWARDED_ALL;

Una riga, tre ecosistemi. Una volta reso attendibile il proxy, il framework legge X-Forwarded-Proto e X-Forwarded-Host, ricostruisce https:// e la firma corrisponde. Rendi attendibili solo i proxy che controlli davvero: con trust proxy impostato a true, qualsiasi cosa a monte può dichiarare lo schema che vuole.

Body JSON: bodySHA256

I webhook di Twilio sono application/x-www-form-urlencoded per impostazione predefinita — mai JSON. Ma Conversations e Studio possono inviare JSON, e allora le regole cambiano: il body non è affatto nei parametri. Twilio ne calcola l'hash SHA-256, lo aggiunge all'URL come parametro di query bodySHA256 e firma l'URL con una lista di parametri vuota.

Validare un webhook JSON (Node)
const valid = twilio.validateRequestWithBody(
  process.env.TWILIO_AUTH_TOKEN,
  req.headers['x-twilio-signature'],
  fullUrl,        // deve ancora contenere ?bodySHA256=…
  rawBody         // la stringa grezza, NON passata da JSON.parse e ri-serializzata
);

Qui ci sono due trappole. Togli la query string dall'URL e togli anche bodySHA256, così il controllo fallisce senza mai nominare il body. E se il tuo framework ha parsato il JSON prima di te, JSON.stringify() non ti restituirà i byte originali — ordine delle chiavi e spazi sono persi. Cattura il body grezzo. La modalità JSON dello strumento qui sopra mostra i due hash affiancati, così vedi quale delle due trappole ti ha colpito.

Dettagli su Twilio da tenere a mente

Dettagli che decidono se la firma avrà mai la possibilità di essere verificata:

  • HMAC-SHA1, non SHA256. Stripe, GitHub e Shopify usano tutti SHA-256, quindi copiare un handler funzionante da uno di loro e cambiare solo il nome dell'header produce un validatore che non corrisponde mai a niente.
  • Il payload è application/x-www-form-urlencoded per impostazione predefinita, mai JSON. Se il tuo framework parsa solo body JSON, la tua mappa di parametri è vuota e ogni firma fallisce.
  • Twilio aspetta 15 secondi per un webhook vocale, 5 secondi su Conversations. Prima valida, poi rispondi, poi fai il lavoro lento — un controllo di firma eseguito dopo una query al database può andare in timeout prima di restituire.
  • Un solo retry per impostazione predefinita, fino a cinque con i connection override. Quindi una firma errata non è un messaggio fallito: è un messaggio perso dopo un paio di tentativi.
  • Il Debugger conserva i log per 30 giorni. È lì che trovi l'URL che Twilio ha davvero chiamato, cioè l'input che serve a questa pagina.
  • Sulle connessioni WebSocket l'header arriva in minuscolo: x-twilio-signature. Confronta gli header senza distinguere maiuscole e minuscole.

E l'errore Twilio 57012?

L'errore 57012 (firma non valida) ti dice che la firma non corrisponde a quella calcolata da Twilio, e indica i soliti sospetti: token sbagliato, URL sbagliato, parametri modificati in transito, hash del body mancante. Tutto vero. Ma si ferma esattamente dove inizia il debug, perché non può dirti quale si applica alla tua richiesta. Nota anche che, dal lato di Twilio, le cause 1, 2, 3, 7 e 8 qui sopra sono tutte solo «URL sbagliato» — cinque bug diversi, cinque correzioni diverse, un solo codice di errore. Separarle è lo scopo di questa pagina.

Quando usare cosa — onestamente

Qui non siamo la risposta a tutto, e fingere il contrario ti farebbe solo perdere il pomeriggio.

La documentazione di Twilio

Stai scrivendo l'handler da zero e vuoi l'algoritmo di riferimento e l'SDK per il tuo linguaggio. È la fonte di verità — webhooks-security copre sette linguaggi. Non ha nessuna sezione di troubleshooting, ed è per questo che sei finito qui.

Hookdeck

Il tuo problema non è una singola richiesta rotta, è la consegna: vuoi retry sotto il tuo controllo, una dead letter queue, timeout oltre i 15 secondi di Twilio, la trasformazione da form a JSON o la verifica della firma all'edge, così che la tua app non veda mai una richiesta non verificata. Quella è una piattaforma, e una vera. Pagala.

Questa pagina

Una firma, adesso, e vuoi sapere quale riga del tuo codice mente. Trenta secondi, niente da installare, nessun account. Poi vai a sistemare la configurazione del proxy e ti dimentichi di noi. È un ottimo risultato.

Se i webhook sono una parte stabile del tuo stack e non un incendio da spegnere, il nostro tunnel Relay inoltra il traffico Twilio reale a localhost, e il tester di webhook Twilio rinvia richieste firmate al tuo handler, così puoi verificare la correzione senza spendere un vero SMS.

Twilio webhook testerRelay

Domande frequenti

Perché la mia firma Twilio fallisce dietro ngrok o un proxy?

Perché il proxy termina il TLS e inoltra HTTP semplice alla tua app. Twilio ha firmato l'URL https://; il tuo framework ricostruisce http:// dal proprio socket. Le stringhe differiscono, l'HMAC differisce. Rendi attendibile il proxy così che X-Forwarded-Proto venga usato per ricostruire l'URL, e tutto torna a corrispondere.

La porta va inclusa nell'URL?

Solo se Twilio l'ha chiamata. Twilio firma l'URL così come è configurato, quindi una 443 standard non compare mai nella stringa. Se aggiungi tu :443 quando ricostruisci l'URL, ottieni una firma diversa. Ricostruisci l'host dall'header Host invece di concatenare hostname e porta.

Come valido un body JSON inviato da Twilio?

Con validateRequestWithBody() invece di validateRequest(). Twilio calcola l'hash SHA-256 del body, lo mette in un parametro di query bodySHA256 e firma l'URL senza parametri. Ti servono i byte grezzi del body e l'URL completo, compreso quel parametro di query.

Perché funziona in locale ma non in produzione?

In sviluppo il tunnel di solito passa alla tua app lo stesso schema usato da Twilio. In produzione, Traefik, nginx o Cloudflare le passano HTTP semplice su una porta interna. Stesso codice, URL ricostruito diverso, quindi la firma smette di corrispondere. È la causa numero uno di questa pagina.

Il mio Auth Token viene inviato ai vostri server?

No. L'HMAC gira nel tuo browser con WebCrypto e lo strumento non invia nessuna richiesta mentre lo usi. Apri la scheda Rete e guarda. Il tuo Auth Token è un segreto di produzione — dovresti porre questa domanda a ogni sito che te lo chiede, compreso il nostro.

La firma Twilio è SHA-256, come quella di Stripe?

No, Twilio usa HMAC-SHA1 codificato in base64. Stripe, GitHub e Shopify usano SHA-256. Adattare un handler Stripe funzionante rinominando l'header ti dà un validatore che rifiuta tutto, ed è un modo comune di arrivare a questo errore.

Offerta di lancio · −50 %

Stai facendo il debug di un'integrazione webhook questa settimana?

Il Pass 7 giorni sblocca le spiegazioni AI e il codice dell'handler pronto da incollare, 25 URL permanenti, 30 giorni di cronologia e il tunnel Relay verso localhost. Un solo pagamento, nessun abbonamento.

5 € 2,50 € una volta
Pagamento unico · nessun abbonamento · rimborso entro 14 giorni · prezzo di lancio fino al 30 novembre
Validazione della firma Twilio fallita: trova la causa · Webhook Toolkit