Every webhook delivery includes an X-Watsi-Signature header. watsi computes the value by hashing the raw request body with HMAC-SHA256 and your subscription secret.
Header format
X-Watsi-Signature: 8a9f0d1c...Treat the header value as a hex-encoded digest. To verify it, recompute the same HMAC on your side and compare the two digests using a constant-time comparison.
Verification algorithm
- Read the raw request body bytes exactly as received.
- Read
X-Watsi-Signaturefrom the request headers. - Compute
HMAC-SHA256(secret, rawBody). - Hex-encode the computed digest.
- Compare the computed digest and header value in constant time.
- Reject the request with 401 if the values do not match.
Code examples
Examples in Node.js, Python, Ruby, Go, and PHP:
import crypto from 'crypto'
import express from 'express'
const app = express()
app.use(
express.json({
verify: (req, _res, buf) => {
req.rawBody = buf
},
})
)
function verifySignature(req) {
const signature = req.headers['x-watsi-signature']
if (!signature || !Buffer.isBuffer(req.rawBody)) return false
const computed = crypto
.createHmac('sha256', process.env.WATSI_WEBHOOK_SECRET ?? '')
.update(req.rawBody)
.digest('hex')
const expectedBuf = Buffer.from(String(signature), 'utf8')
const computedBuf = Buffer.from(computed, 'utf8')
if (expectedBuf.length !== computedBuf.length) return false
return crypto.timingSafeEqual(expectedBuf, computedBuf)
}
app.post('/webhooks/watsi', (req, res) => {
if (!verifySignature(req)) {
return res.status(401).json({ error: 'Invalid signature' })
}
console.log('Verified event', req.body.type)
res.status(200).json({ received: true })
})Operational tips
| Practice | Why it helps |
|---|---|
| Use the raw request body | Hash the exact bytes that arrived over HTTP before any JSON parsing or re-serialization. |
| Compare in constant time | Use timing-safe equality helpers to avoid leaking information through string comparison timing. |
| Store secrets outside code | Keep your signing secret in an environment variable or secret manager, never in the repository. |
| Log the delivery id | Persist X-Watsi-Delivery-Id or event.id so retries can be correlated cleanly. |