Ensure that incoming webhooks are authentic webhooks coming from Aikido

How to verify incoming webhooks

In order to verify that incoming webhooks from Aikido are authentic, we send along a security header which is a HMAC digest generated from the payload and signed with a HMAC signing secret.

How to create a HMAC signing secret in Aikido

A secret can be created on the webhooks integration settings page (here) and then clicking "Add secret". When it is created successfully, we will show you the raw signing secret once. Copy it and keep it secure.

How can I verify a webhook

When you created a HMAC signing secret, Aikido will include the X-Aikido-Webhook-Signature header with each request. This header's value will be a HMAC digest from the payload as a JSON string. To verify the authenticity of the request, you need to create your own HMAC digest based on the payload and signed with your HMAC secret. If the digest from the header matches with the one you generated, the request is authentic.

In order to protect against replay attacks, we include an epoch timestamp in the webhook payload from right before we dispatch the HTTP request. This timestamp is included as the dispatched_at property and should not be older than 30 seconds when you verify the payload.

The process to verify an incoming webhook generally looks like this:

  1. Get the signature from the X-Aikido-Webhook-Signature request header
  2. Parse the request body back to a JSON string
  3. Create a hmac digest from the stringified request body with the sha256 algorithm and sign it with the HMAC secret you created in Aikido
  4. Validate that the signature from the request header matches with the digest you just generated.
  5. Validate that the dispatched_at epoch timestamp from the property is not older than 30 seconds

Below we’ll share some pseudo JavaScript showing how you can verify the hash.

const { createHmac } = require('node:crypto');

function isIncomingWebhookAuthentic(payload, signature) {
	// get the raw webhook secret from the environment variables
	const hmacSigningSecret = process.env.AIKIDO_HMAC_SIGNING_SECRET;

	// lets create the hmac instance
	const hmac = createHmac('sha256', hmacSigningSecret);

	// lets convert the payload object to a JSON string
	const rawPayload = JSON.stringify(payload);

	// update the hmac content with the stringified payload
	hmac.update(rawPayload);

	// calculate the digest of the hmac content and return it as a hex value
	const payloadDigest = hmac.digest('hex');

	// if digest does not match the provided signature, the webhook is invalid
	if (payloadDigest !== signature) return false;

	// get the current epoch timestamp
	const currentEpochTimestamp = Math.floor(new Date().getTime() / 1000);
	
	// if the dispatched_at epoch timestamp from the payload is longer than 30 seconds old, the webhook is invalid
	if ((currentEpochTimestamp - (payload['dispatched_at'] ?? 0)) > 30) return false;

	// webhook passed all checks and is valid
	return true;
}