Additionally, it will include the X-Hypertune-Signature header which will be the HMAC-SHA-256 digest in hexadecimal format of the raw body, using the webhook secret.
We will retry webhook sends with exponential backoff until a limit, so your implementation should be idempotent and handle missing hooks.
Processing webhooks
To process a request you should:
Verify it is from Hypertune. To do this, validate that the X-Hypertune-Signature matches the definition above, using the secret you configured when setting up your webhook.
Within 10 seconds, respond with a successful status code: one in the range 200-299 inclusive.
import { createHmac, timingSafeEqual } from 'node:crypto'
import express from 'express'
// Do not hardcode in a production application
const WEBHOOK_SECRET = '1de87274cc2ddba774cbcec5a9ad8727'
const app = express()
// If you want to use body parser JSON, do something like this:
// https://github.com/stripe/stripe-node/issues/341#issuecomment-304733080
app.use(express.raw({ type: '*/*' }))
app.post('/', (req, res) => {
const actualSignature = [
req.headers['x-hypertune-signature'],
].flat()[0]
if (!actualSignature) {
console.log(
'Rejected webhook request with missing signature'
)
res.status(401).send('Missing X-Hypertune-Signature header')
return
}
const expectedSignature = createHmac('sha256', WEBHOOK_SECRET)
.update(req.body.toString())
.digest('hex')
if (
actualSignature.length !== expectedSignature.length ||
!timingSafeEqual(
Buffer.from(actualSignature),
Buffer.from(expectedSignature)
)
) {
console.log('Rejected webhook request with bad signature')
res.status(403).send('Bad signature')
return
}
console.log('Received valid webhook event')
const data = JSON.parse(req.body.toString())
// TODO: Do something interesting with the data here
res.sendStatus(204)
})
const port = 8080
app.listen(port)
console.log(`Server listening at http://localhost:${port}`)