Webhook Security
Hash-based Message Authentication Code (HMAC)
Complexity |
|
Pros |
|
Caveats |
|
Examples |
Hash-based Message Authentication Code (HMAC) is, by far, the most popular authentication and message security method used on webhook requests, including 65% of the webhooks we studied. In this method, the webhook provider and listener use a secret key to sign and validate webhook requests.
On webhook requests, the provider signs the webhook message using the secret key plus a hashing algorithm — typically
HMAC-SHA256
, encodes the resulting signature inbase64
orhex
, and then includes the signature in the webhook request as a header.The webhook listener receives the request and repeats the same steps — signs and encodes the webhook message using the secret key — and compares the resulting signature with the value sent in the request header. If the result matches, the request is considered legitimate.
const signatureHeader = 'Signature-Header' const signatureAlgorithm = 'sha256' const encodeFormat = 'hex' const hmacSecret = process.env.WEBHOOK_SECRET app.post('/webhook', (req, res) => { // Create digest with payload + hmac secret const hashPayload = req.rawBody const hmac = crypto.createHmac(signatureAlgorithm, hmacSecret) const digest = Buffer.from(signatureAlgorithm + '=' + hmac.update(hashPayload).digest(encodeFormat), 'utf8') // Get hash sent by the provider const providerSig = Buffer.from(req.get(signatureHeader) || '', 'utf8') // Compare digest signature with signature sent by provider if (providerSig.length !== digest.length || !crypto.timingSafeEqual(digest, providerSig)) { res.status(401).send('Unauthorized') }else{ // Webhook Authenticated // process and respond... res.json({ message: "Success" }) } })
Request Signature Validation
HMAC vs. Shared Secrets
HMAC offers the following advantages over Basic Authentication:
- Authentication + message integrity: Assuming the secret key is known only by the webhook provider and the listener, the HMAC process verifies that the message comes from the webhook provider (authenticity) and its contents are the same as they were at the time of sending (integrity).
- The secret key remains secret: In HMAC, secret keys are not sent with the webhook request — only the signatures created with it — reducing the risk of stolen keys.
HMAC is only as good as its implementation
Like any other security control, HMAC is only as good as its implementation. In our research, we saw many examples of webhook providers with unnecessary complexity, lack of features, and complex documentation that made their solutions tough to implement and keep safe. Good webhook implementations will typically:
- Use strong hash algorithms such as
sha256
andsha512
- Add sensitive headers to the hash digest:
... const clientIdHeader = 'clientid' ... app.post('/webhook', (req, res) => { // Create digest from the request payload and the clientid header const hashPayload = req.rawBody+'.'+req.get(clientIdHeader) const hmac = crypto.createHmac(signatureAlgorithm, hmacSecret) const digest = Buffer.from(signatureAlgorithm + '=' + hmac.update(hashPayload).digest(encodeFormat), 'utf8') ... })
- Leverage HMAC signatures to implement replay prevention, versioning, and key rotation
- Provide great documentation and features for better operations