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:
- Get the signature from the
X-Aikido-Webhook-Signature
request header - Parse the request body back to a JSON string
- Create a hmac digest from the stringified request body with the
sha256
algorithm and sign it with the HMAC secret you created in Aikido - Validate that the signature from the request header matches with the digest you just generated.
- 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;
}