Launch-Angebot: −50 % auf deine erste Zahlung, bis 30. November.Zum Angebot →
webhook·toolkit
Twilio

Twilio-Signaturprüfung fehlgeschlagen — finde heraus, warum

Zuletzt aktualisiert: Juli 2026

Du hast einen X-Twilio-Signature-Header, dein Code sagt, er passt nicht, und Twilios Doku antwortet mit einem SDK zum Installieren. Das sagt dir nicht, was falsch ist: deine URL, deine Parameter oder dein Token. Also füg alle drei unten ein: Wir berechnen die Signatur für jede plausible URL-Form neu und zeigen dir die, die passt. Die passende Zeile ist deine Diagnose.

Kurz gesagt: Eine Twilio-Signatur scheitert fast immer, weil die URL, die dein Code rekonstruiert, nicht byte-identisch mit der URL ist, die Twilio aufgerufen hat — meist http:// statt https:// hinter einem Reverse Proxy. Der Algorithmus ist selten das Problem.

Eine von Twilio abgelehnte Signatur prüfen

Alles läuft in deinem Browser (WebCrypto HMAC-SHA1). Dein Auth Token wird nie an unsere Server gesendet, und solange du das Tool nutzt, verlässt kein Request diese Seite. Öffne den Netzwerk-Tab und prüf es nach — uns ist lieber, du tust das.

Der exakte String, den Twilio signiert

Alle anderen Abschnitte dieser Seite ergeben sich aus diesem. Twilio signiert weder deinen Body noch deine Headers. Es signiert einen einzigen String, der so aufgebaut wird:

  1. Beginne mit der vollständigen URL, die Twilio aufgerufen hat: Schema, Host, Port (falls explizit), Pfad und Query-String.
  2. Sortiere die Namen der POST-Parameter in ASCII-Reihenfolge.
  3. Hänge für jeden den Namen an, direkt gefolgt von seinem Wert. Kein Trennzeichen, kein Gleichheitszeichen, keine Kodierung, nichts dazwischen.
  4. Bilde über den ganzen String einen HMAC-SHA1 mit deinem Auth Token.
  5. Kodiere den Digest in Base64. Das ist X-Twilio-Signature.

Dasselbe mit echten Werten

Von Twilio aufgerufene URL
https://api.example.com/twilio/sms
POST-Parameter (form-urlencoded)
AccountSid=AC1234567890abcdef
Body=Hello
From=%2B15017122661
To=%2B15558675310
Der signierte String — 101 Zeichen, aneinandergehängt
https://api.example.com/twilio/smsAccountSidAC1234567890abcdefBodyHelloFrom+15017122661To+15558675310
Auth Token (ein Wegwerf-Token für das Beispiel)
12345678901234567890123456789012
base64(HMAC-SHA1(string, token))
REsmqJpWLoqe1Q/VJXTZjx4eviY=

Achte darauf, was nicht drinsteht. Kein &, kein =, kein Percent-Encoding: Der Wert ist +15017122661, nicht %2B15017122661. Wer die Parameter selbst durch Aufsplitten des rohen Bodys rekonstruiert, vergisst meist das Dekodieren — und bekommt eine Signatur, die aus einem Grund falsch ist, den keine Fehlermeldung je nennen wird.

Ändere ein Byte dieser 101 Zeichen, und die Signatur ändert sich komplett. Das ist die ganze Geschichte: Dein Code und Twilio müssen exakt denselben String bauen, und jeder Proxy, Router und jede Middleware dazwischen kann ihn ändern, ohne dir Bescheid zu sagen.

Vier URLs, vier Signaturen, eine Wahrheit

Derselbe Auth Token, dieselben vier Parameter wie oben. Nur die URL ändert sich — und das reicht schon:

Für den String verwendete URLResultierende SignaturWoher sie kommt
https://api.example.com/twilio/smsREsmqJpWLoqe1Q/VJXTZjx4eviY=Was Twilio signiert
http://api.example.com/twilio/smsNpKfrAiY/l6i2mXxfWlrjyRkytY=Was deine App hinter SSL-Terminierung sieht
https://api.example.com:443/twilio/smsvMbUG7KsMVUK80lzK6a+4IMFPqU=So sieht eine von Hand gebaute URL aus
https://api.example.com/twilio/sms/8LRHF+mPXykd0Jij9WhgsI7agMs=Was ein Router liefert, der Pfade normalisiert

Nichts am Fehler verrät dir, bei welcher der vier du bist. Genau das macht das Tool: Es berechnet sie alle, bis zu sechzehn URL-Formen, und vergleicht jede mit der Signatur, die Twilio tatsächlich gesendet hat. Die grüne Zeile ist die Antwort.

Die 8 echten Ursachen

Sortiert danach, wie oft sie im Twilio-Community-Forum und in StackOverflow-Threads zu diesem Fehler auftauchen. Diese Reihenfolge ist unsere Lesart, keine gemessene Statistik — nimm sie als Suchreihenfolge, nicht als Fakt.

#UrsacheDas Symptom, das sie verrätLösung
1SSL-Terminierung (X-Forwarded-Proto)Klappt mit curl auf localhost, scheitert, sobald ein Load Balancer davorsteht. Dein Log zeigt http://, die Console zeigt https://.Vertrau dem Proxy: app.set('trust proxy', true), ProxyFix(app.wsgi_app, x_proto=1), TrustedProxies bei Laravel.
2Expliziter Port in der rekonstruierten URLDein Log zeigt https://host:443/… — Twilio schreibt nie einen impliziten Port.Aus dem Header Host / X-Forwarded-Host aufbauen. Hostname und Port nicht zusammensetzen.
3Query-String entfernt oder neu kodiertNur URLs mit ?params scheitern. Dein Framework hat das Routenmuster geliefert, nicht das rohe Ziel.Das rohe Request-Ziel verwenden (req.originalUrl, request.url) und die Kodierung nicht anfassen.
4Parameter vor der Prüfung verändertScheitert nur bei manchen Nachrichten — denen mit einem leeren Feld oder abschließenden Leerzeichen.Prüfen, bevor irgendeine Middleware den Body anfasst. Python: parse_qs(…, keep_blank_values=True). Laravel: die Route von TrimStrings ausnehmen.
5JSON-Body als Formulardaten geprüftConversations- und Studio-Webhooks scheitern, normale SMS-Webhooks klappen.validateRequestWithBody() und den Query-Parameter bodySHA256 verwenden.
6Sekundärer Auth Token nicht hochgestuftScheitert direkt nach einer Token-Rotation, bei 100 % der Requests.Console → Account → Auth Token: Ein sekundärer Token signiert nichts, bis du ihn zum Primary hochstufst.
7Wiederholte Parameter (SDK-Bug)Nur Gruppennachrichten und Conversations scheitern, und nicht reproduzierbar.twilio-node #722: params['MessagingBinding.Address'].sort() vor validateRequest().
8Proxy-Rewrite ändert den PfadScheitert nur in Produktion. Twilio ruft /webhooks/sms auf, deine App empfängt /sms.Gegen den öffentlichen Pfad prüfen, nicht gegen den internen, der nach dem Rewrite übrig bleibt.

Der Bug mit wiederholten Parametern — nicht deine Schuld

Dieser verdient einen eigenen Abschnitt, denn du kannst eine Stunde in Twilios Doku lesen und ihn nie finden. Gruppennachrichten und Conversations senden denselben Key mehrfach:

Der Body, den Twilio per POST sendet
MessagingBinding.Address=%2B15558675310&MessagingBinding.Address=%2B15017122661

Twilios Server sortiert die Keys und verkettet dann. Deine Parameter-Map enthält ein Array. twilio-node macht data += key + params[key], und JavaScript wandelt dieses Array in einen String um, indem es die Elemente mit Kommas verbindet — in Ankunftsreihenfolge. Hat dein Framework die beiden Adressen in einer anderen Reihenfolge geparst als Twilios Server, unterscheiden sich die beiden Strings, und die Prüfung scheitert. Derselbe Token, dieselbe URL, dieselben Parameter. Scheitert trotzdem.

Zwei Reihenfolgen, zwei Signaturen — alles andere identisch
Ankunftsreihenfolge: +15558675310, dann +15017122661LJaNX3wj9/RRc4+GG1kwgPndM1c=
Sortiert: +15017122661, dann +15558675310A7jNj66u8x1pDaqy3CNma9Q0XvA=
Der Workaround
// twilio-node issue #722
// Wiederholte Parameter kommen als Array an; vor der Prüfung sortieren.
if (Array.isArray(params['MessagingBinding.Address'])) {
  params['MessagingBinding.Address'].sort();
}
const valid = twilio.validateRequest(authToken, signature, url, params);

Das Tool testet beide Reihenfolgen automatisch. Wird die sortierte Zeile grün, hast du einen Bug im SDK gefunden, nicht in deinem Code — und kannst aufhören, deinen Handler zum zehnten Mal zu lesen.

Klappt lokal, bricht in Produktion

Die mit Abstand häufigste Geschichte, und sie hat eine einzige Ursache: In der Entwicklung übergibt dein Tunnel den Request mit demselben Schema an deine App, das Twilio verwendet hat. In Produktion terminieren Traefik, nginx oder Cloudflare TLS und leiten reines HTTP auf Port 3000 weiter. Dein Framework meldet, was es auf seinem Socket sieht — http:// — und signiert einen String, den Twilio nie gebaut hat.

Express
// Hinter Traefik / nginx / Cloudflare
app.set('trust proxy', true);
// req.protocol folgt jetzt 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 rekonstruiert jetzt https://
validator.validate(request.url, request.form, signature)
Laravel
// app/Http/Middleware/TrustProxies.php
protected $proxies = '*';           // nur, wenn der Proxy dir gehört
protected $headers = Request::HEADER_X_FORWARDED_ALL;

Eine Zeile, drei Ökosysteme. Sobald dem Proxy vertraut wird, liest das Framework X-Forwarded-Proto und X-Forwarded-Host, rekonstruiert https://, und die Signatur passt. Vertrau nur Proxys, die du wirklich kontrollierst: Mit trust proxy auf true kann alles davor jedes beliebige Schema behaupten.

JSON-Bodys: bodySHA256

Twilio-Webhooks sind standardmäßig application/x-www-form-urlencoded — nie JSON. Conversations und Studio können aber JSON senden, und dann ändern sich die Regeln: Der Body steckt gar nicht in den Parametern. Twilio hasht ihn mit SHA-256, hängt diesen Hash als Query-Parameter bodySHA256 an die URL und signiert die URL mit einer leeren Parameterliste.

Einen JSON-Webhook prüfen (Node)
const valid = twilio.validateRequestWithBody(
  process.env.TWILIO_AUTH_TOKEN,
  req.headers['x-twilio-signature'],
  fullUrl,        // muss weiterhin ?bodySHA256=… enthalten
  rawBody         // der rohe String, NICHT per JSON.parse geparst und neu serialisiert
);

Hier lauern zwei Fallen. Entfernst du den Query-String aus der URL, entfernst du auch bodySHA256, und die Prüfung scheitert, ohne den Body je zu erwähnen. Und hat dein Framework das JSON schon geparst, bevor du drankommst, gibt dir JSON.stringify() die Original-Bytes nicht zurück — Key-Reihenfolge und Leerzeichen sind weg. Erfasse den rohen Body. Der JSON-Modus im Tool oben zeigt beide Hashes nebeneinander, damit du siehst, welche der beiden dich erwischt hat.

Twilio-Fakten, die du im Kopf haben solltest

Details, die entscheiden, ob die Signatur überhaupt eine Chance bekommt, geprüft zu werden:

  • HMAC-SHA1, nicht SHA256. Stripe, GitHub und Shopify nutzen alle SHA-256 — wer einen funktionierenden Handler von einem davon kopiert und nur den Header-Namen tauscht, bekommt einen Validator, der nie etwas matcht.
  • Die Payload ist standardmäßig application/x-www-form-urlencoded, nie JSON. Parst dein Framework nur JSON-Bodys, ist deine Parameter-Map leer, und jede Signatur scheitert.
  • Twilio wartet bei einem Voice-Webhook 15 Sekunden, bei Conversations 5 Sekunden. Erst prüfen, antworten, dann die langsame Arbeit erledigen — eine Signaturprüfung nach einem Datenbankaufruf kann in den Timeout laufen, bevor sie zurückkommt.
  • Standardmäßig ein Retry, mit Connection Overrides bis zu fünf. Eine falsche Signatur ist also nicht eine fehlgeschlagene Nachricht, sondern eine Nachricht, die nach ein paar Versuchen verloren ist.
  • Der Debugger bewahrt seine Logs 30 Tage auf. Dort findest du die URL, die Twilio tatsächlich aufgerufen hat — genau der Input, den diese Seite braucht.
  • Bei WebSocket-Verbindungen kommt der Header kleingeschrieben an: x-twilio-signature. Vergleiche Header-Namen ohne Beachtung der Groß-/Kleinschreibung.

Und der Twilio-Fehler 57012?

Fehler 57012 (ungültige Signatur) sagt dir, dass die Signatur nicht zu der passt, die Twilio berechnet hat, und nennt die üblichen Verdächtigen: falscher Token, falsche URL, unterwegs geänderte Parameter, fehlender Body-Hash. Alles richtig. Aber er hört genau dort auf, wo das Debugging anfängt, denn er kann dir nicht sagen, was davon auf deinen Request zutrifft. Beachte außerdem: Aus Twilios Sicht sind die Ursachen 1, 2, 3, 7 und 8 oben alle nur „falsche URL“ — fünf verschiedene Bugs, fünf verschiedene Lösungen, ein Fehlercode. Sie auseinanderzuhalten ist der Zweck dieser Seite.

Wann was — ehrlich

Wir sind hier nicht die Antwort auf alles, und so zu tun, würde dir den Nachmittag kosten.

Twilios Doku

Du schreibst den Handler von Grund auf und willst den Referenzalgorithmus und das SDK für deine Sprache. Sie ist die maßgebliche Quelle — webhooks-security deckt sieben Sprachen ab. Einen Troubleshooting-Abschnitt hat sie überhaupt nicht, deshalb bist du hier gelandet.

Hookdeck

Dein Problem ist nicht ein kaputter Request, sondern die Zustellung: Du willst Retries, die du steuerst, eine Dead Letter Queue, Timeouts über Twilios 15 Sekunden hinaus, Form-zu-JSON-Transformation oder Signaturprüfung am Edge, damit deine App nie einen ungeprüften Request sieht. Das ist eine Plattform, und zwar eine echte. Zahl dafür.

Diese Seite

Eine Signatur, jetzt sofort, und du willst wissen, welche Zeile deines Codes lügt. Dreißig Sekunden, nichts zu installieren, kein Konto. Dann korrigierst du die Proxy-Konfiguration und vergisst, dass es uns gibt. Das ist ein gutes Ergebnis.

Sind Webhooks ein fester Teil deines Stacks und nicht nur ein Brand, den du gerade löschst, leitet unser Relay-Tunnel echten Twilio-Traffic an localhost weiter, und der Twilio-Webhook-Tester spielt signierte Requests gegen deinen Handler ab, damit du den Fix prüfen kannst, ohne eine echte SMS zu verbrauchen.

Twilio Webhook TesterRelay

Häufige Fragen

Warum scheitert meine Twilio-Signatur hinter ngrok oder einem Proxy?

Weil der Proxy TLS terminiert und reines HTTP an deine App weiterleitet. Twilio hat die https://-URL signiert; dein Framework rekonstruiert http:// aus seinem eigenen Socket. Die Strings unterscheiden sich, der HMAC auch. Vertrau dem Proxy, damit X-Forwarded-Proto zum Rekonstruieren der URL verwendet wird, und sie passt wieder.

Gehört der Port in die URL?

Nur, wenn Twilio ihn aufgerufen hat. Twilio signiert die URL wie konfiguriert, ein Standard-443 taucht im String also nie auf. Hängst du beim Rekonstruieren selbst :443 an, bekommst du eine andere Signatur. Rekonstruiere den Host aus dem Host-Header, statt Hostname und Port zusammenzusetzen.

Wie prüfe ich einen JSON-Body von Twilio?

Mit validateRequestWithBody() statt validateRequest(). Twilio hasht den Body mit SHA-256, legt den Hash in einen Query-Parameter bodySHA256 und signiert die URL ohne Parameter. Du brauchst die rohen Body-Bytes und die vollständige URL inklusive dieses Query-Parameters.

Warum klappt es lokal, aber nicht in Produktion?

In der Entwicklung gibt dein Tunnel deiner App meist dasselbe Schema, das Twilio verwendet hat. In Produktion reichen Traefik, nginx oder Cloudflare reines HTTP auf einem internen Port weiter. Gleicher Code, andere rekonstruierte URL, also passt die Signatur nicht mehr. Das ist die Ursache Nummer eins auf dieser Seite.

Wird mein Auth Token an eure Server gesendet?

Nein. Der HMAC läuft mit WebCrypto in deinem Browser, und das Tool sendet keinen Request, solange du es nutzt. Öffne deinen Netzwerk-Tab und sieh zu. Dein Auth Token ist ein Produktions-Secret — das solltest du jede Website fragen, die danach verlangt, unsere eingeschlossen.

Ist die Twilio-Signatur SHA-256, wie bei Stripe?

Nein, Twilio nutzt HMAC-SHA1 und kodiert das Ergebnis in Base64. Stripe, GitHub und Shopify nutzen SHA-256. Wer einen funktionierenden Stripe-Handler durch Umbenennen des Headers anpasst, bekommt einen Validator, der alles ablehnt — ein häufiger Weg zu genau diesem Fehler.

Launch-Angebot · −50 %

Debuggst du diese Woche eine Webhook-Integration?

Der 7-Tage-Pass schaltet KI-Erklärungen und fertigen Handler-Code frei, dazu 25 dauerhafte URLs, 30 Tage Verlauf und den Relay-Tunnel zu localhost. Einmal zahlen, kein Abo.

5 € 2,50 € einmalig
Einmalzahlung · kein Abo · 14 Tage Geld-zurück · Launch-Preis bis 30. November
Twilio-Signaturprüfung fehlgeschlagen: die Ursache finden · Webhook Toolkit