---
title: Verifying Webhooks Manually
---
# Verifying Webhooks Manually

<Callout type="info">

The recommended way to verify webhooks is using our official libraries as outlined in the [How to Verify](./how.mdx) section.

<ReceivingSkillCopy />

</Callout>
However, here are instructions for verifying manually for those who need it.

## Verifying signatures manually

Each webhook call includes three headers with additional information that are used for verification:

* `svix-id`: the unique message identifier for the webhook message. This identifier is unique across all messages, but will be the same when the same webhook is being resent (e.g. due to a previous failure).
* `svix-timestamp`: timestamp in [seconds since epoch](https://en.wikipedia.org/wiki/Unix_time).
* `svix-signature`: the [Base64](https://en.wikipedia.org/wiki/Base64) encoded list of signatures (space delimited).

<Callout type="info">

Professional and Enterprise tier customers can have the headers white-labeled to use the `webhook-` prefix instead of the `svix-` prefix used above. The Svix libraries support both.

</Callout>
### Constructing the signed content

The content to sign is composed by concatenating the id, timestamp and payload, separated by the full-stop character (`.`). In code, it will look something like:

```javascript
const signedContent = `${svix_id}.${svix_timestamp}.${body}`;
```

Where `body` is the raw body of the request. The signature is sensitive to any changes, so even a small change in the body will cause the signature to be completely different. This means that you should *not* change the body in any way before verifying.

### Determining the expected signature

Svix uses an [HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) with [SHA-256](https://en.wikipedia.org/wiki/SHA-2) to sign its webhooks.

So to calculate the expected signature, you should HMAC the `signed_content` from above using the base64 portion of your signing secret (this is the part after the `whsec_` prefix) as the key.
For example, given the secret `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw` you will want to use `MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw`.

For example, this is how you can calculate the signature in Node.js:

```javascript
const crypto = require('crypto');

const signedContent = `${svix_id}.${svix_timestamp}.${body}`;
const secret = "whsec_5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH";

// Need to base64 decode the secret
const secretBytes = Buffer.from(secret.split('_')[1], "base64");
const signature = crypto
  .createHmac('sha256', secretBytes)
  .update(signedContent)
  .digest('base64');

console.log(signature);
```

This generated signature should match one of the ones sent in the `svix-signature` header.

The `svix-signature` header is composed of a list of space delimited signatures and their corresponding version identifiers. The signature list is most commonly of length one. Though there could be any number of signatures. For example:

```
v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo= v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo=
```

Make sure to remove the version prefix and delimiter (e.g. `v1,`) before verifying the signature.

Please note that to compare the signatures it's recommended to use a constant-time string comparison method in order to prevent timing attacks.

### Verify timestamp

As mentioned above, Svix also sends the timestamp of the attempt in the `svix-timestamp` header. You should compare this timestamp against your system timestamp and make sure it's within your tolerance in order to prevent timestamp attacks.


### Example signatures

Here is an example you can use to verify you implemented everything correctly. Please note that this may fail verification due to the timestamp being old.

```javascript
secret = 'whsec_plJ3nmyCDGBKInavdOK15jsl';
payload = '{"event_type":"ping","data":{"success":true}}';
msg_id = 'msg_loFOjxBNrRLzqYUf';
timestamp = '1731705121';

// Would generate the following signature:
signature = 'v1,rAvfW3dJ/X/qxhsaXPOyyCGmRKsaKWcsNccKXlIktD0=';
```

Additionally, you can use the [webhook simulation tool](https://www.standardwebhooks.com/simulate) to generate as many examples as you need.
