Atomic logo

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:

  1. a live webhook endpoint that is receiving event data
  2. a subscription to at least one webhook event in the Atomic Console pointing to your live webhook endpoint
Note that your webhook endpoint must be completely controlled by you and not set up via a webhook testing service, such as webhook.site, otherwise you will not be able to properly secure the 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.

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.

The API secret used to create the HMAC signature is the one assigned to the webhook endpoint (its 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.

  1. Re-create the HMAC signature using the JSON request body and the API secret assigned to the endpoint.
  2. Compare the HMAC signature you created with the one included in the X-HMAC-Signature-Sha-256 header. If the values match, proceed to processing the webhook event data and respond with a 2xx response. If they do not, reject the request with a 401 or 403 response.
  3. 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 2xx response.

Below you will find an example written in NodeJS with ExpressJS utilizing the middleware pattern.

server.js
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}`))
middleware.js
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.

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.

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.

That's it! You now have a webhook endpoint that is securely receiving webhook events. If you would like to review additional topics regarding webhooks, take a look at these guides: