hooksig / Twilio
HMAC-SHA1 · base64 · no timestamp
Twilio
The SHA-1 outlier: it signs the URL plus your form params, sorted by name.
Try it — live, in your browser
Request — edit anything
Verify this in code
import { verify } from "hooksig";
const result = await verify("twilio", {
payload: rawBody,
headers: request.headers,
secret: env.WEBHOOK_SECRET,
url: request.url, // twilio signs the URL
});
if (!result.verified) {
return new Response(result.reason, { status: 400 });
}no match — signature invalid
Signed string
HMAC-SHA1( secret, signed string ) = expected
Expected vs provided
expected
provided
How it's built
The scheme
- Header
X-Twilio-Signature - AlgorithmHMAC-SHA1
- Encodingbase64
- Timestampnone — add your own if you need it
- SignsHMAC-SHA1 over the URL followed by the POST params sorted by name and concatenated, base64. Keyed by the Auth Token.
Gotchas
- It is SHA-1, not SHA-256. Every other provider here is SHA-256.
- The signed data is the full URL, then each form param sorted by name and joined as name+value, no separators.
- Use the exact URL Twilio called, including scheme, host, and query string. A proxy or trailing slash breaks it.
- This covers form-encoded webhooks. JSON-body webhooks use a bodySHA256 query param instead and are not handled here.