-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
Problem
Twilio webhook signature validation is failing in the Cloudflare Worker environment despite passing local tests. This is causing all incoming MMS messages to be rejected as invalid.
Current Implementation
// We construct the validation string from URL and form parameters
const url = new URL(request.url);
const isValid = validateTwilioRequest(
url.toString(),
formEntries,
request.headers.get('X-Twilio-Signature') || '',
env.TWILIO_AUTH_TOKEN,
);Logs
Two failed validation attempts from production:
POST https://tree-spotter.mike-gehard.workers.dev/sms - Ok @ 5/25/2025, 11:58:21 PM
[2025-05-25T23:58:21.577Z] Incoming request to /sms
... all parameters logged correctly ...
Signature: zHbKAIKAkQjLSNyoqt9XtHlIB7o=
URL: https://tree-spotter.mike-gehard.workers.dev/sms
(warn) Invalid Twilio signature received
POST https://tree-spotter.mike-gehard.workers.dev/sms - Ok @ 5/26/2025, 12:06:41 AM
[2025-05-26T00:06:41.108Z] Incoming request to /sms
... all parameters logged correctly ...
Signature: EMxxdUIUJrgE5pSnhL66qG+za5c=
URL: https://tree-spotter.mike-gehard.workers.dev/sms
(warn) Invalid Twilio signature received
Analysis
The second request matches our test case exactly but still fails in production. Key observations:
- The tests pass locally using Node.js
- The validation fails consistently in Cloudflare Workers
- The same request data that works in tests fails in production
Potential Causes
-
URL Normalization
- Cloudflare might be handling the URL differently than our local environment
- The
URLobject in Cloudflare Workers might have subtle differences - Query parameters or trailing slashes might be handled differently
-
Parameter Order
- While we sort the parameters alphabetically, there might be encoding differences
- Form data might be processed differently in Cloudflare Workers vs Node.js
-
Crypto Implementation
- Cloudflare Workers uses WebCrypto API while our local tests use Node's crypto
- There might be subtle differences in how HMAC-SHA1 is implemented
Next Steps
- Add Debug Logging:
// Add to validateTwilioRequest:
console.log('Data being signed:', dataToSign);
console.log('Generated signature:', expectedSignature);- Try URL Normalization:
// Consider using just the pathname
const urlForSigning = new URL(request.url).pathname;-
Compare with Official Implementation:
- Review Twilio's official validation code
- Consider using @twilio/runtime-handler if compatible with Cloudflare Workers
-
Test Different URL Formats:
- Test with/without trailing slash
- Test with different URL normalizations
- Compare URL handling between Node.js and Cloudflare Workers