How to Secure Webhook Endpoints
This guide will walk you through the process of securing your webhook endpoints. To get started you will need the following, both of which can be set up by following both the How to Set Up Webhooks Guide and How to Test Webhook Endpoints Guide:
- a live webhook endpoint that is receiving event data
- a subscription to at least one webhook event in the Atomic Console pointing to your live webhook endpoint
After completing this guide, you will have a webhook endpoint up and running that is securely receiving data from Atomic and rejecting content that is not sent by Atomic.
HMAC signature validation
To secure your webhook endpoint, you need to set up HMAC signature validation. When Atomic sends a webhook event, we include an X-HMAC-Signature-Sha-256 header with the request. This header is an HMAC-SHA256 hash of the request body with one of your API secrets used as the key. By validating the HMAC signature with the correct API secret, you can ensure the request came from Atomic and hasn't been tampered with.
secretId). You can see which secret each endpoint uses in the Atomic Console or by calling the Get Endpoints API. The process of validating the HMAC signature is relatively straightforward, though implementation details will differ slightly depending on the method you used to build your webhook endpoint.
- Re-create the HMAC signature using the JSON request body and the API secret assigned to the endpoint.
- Compare the HMAC signature you created with the one included in the
X-HMAC-Signature-Sha-256header. If the values match, proceed to processing the webhook event data and respond with a2xxresponse. If they do not, reject the request with a401or403response. - Follow the steps in the How to Test Webhook Endpoints guide to ensure your endpoint receives data. If the signature validation is working properly, your endpoint will send a
2xxresponse.
HMAC signature validation example
Below you will find an example written in NodeJS with ExpressJS utilizing the middleware pattern.
const express = require('express')
const { verifyAtomicWebhook } = require('./middleware')
const app = express()
const port = process.env.PORT || 3000
// Parse JSON bodies so req.body is available to the middleware
app.use(express.json())
app.post('/atomic-webhook', verifyAtomicWebhook, (req, res) => {
// process req.body, then send a 2xx so Atomic knows you received it
res.sendStatus(200)
})
app.listen(port, () => console.log(`Listening on port ${port}`))const crypto = require('crypto')
// The API secret assigned to this webhook endpoint (its secretId)
const secret = process.env.ATOMIC_API_SECRET
exports.verifyAtomicWebhook = (req, res, next) => {
const expected = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(req.body))
.digest('hex')
const received = req.header('X-HMAC-Signature-Sha-256') || ''
const expectedBuffer = Buffer.from(expected)
const receivedBuffer = Buffer.from(received)
const isValid =
expectedBuffer.length === receivedBuffer.length &&
crypto.timingSafeEqual(expectedBuffer, receivedBuffer)
if (!isValid) {
return res.status(401).send('Invalid signature')
}
next()
} Set ATOMIC_API_SECRET to the API secret assigned to the endpoint. The signature is computed over the JSON-serialized request body, so parse the body as JSON before verifying it.
IP address allowlist
For an additional layer of security, you can only allow requests from IPs that belong to Atomic. We publish our allowlist in the Atomic Console.
Mutual TLS
If your security policy requires certificate-based authentication of inbound connections, Atomic can present a client certificate on every webhook delivery for your endpoint to verify. This is optional and enabled on request. See the Mutual TLS for Webhooks guide for details.