# Verifying webhooks manually

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

* **svix-id**: The unique message identifier for the webhook message. This identifier is unique across all messages but will remain the same when the same webhook is being resent (e.g., due to a previous failure).
* **svix-timestamp**: The timestamp in seconds since the epoch.
* **svix-signature**: The Base64-encoded list of signatures, space-delimited.

{% hint style="warning" %}
**USE THE RAW REQUEST BODY**

You need to use the raw request body when verifying webhooks, as the cryptographic signature is sensitive to even the slightest changes. You should watch out for frameworks that parse the request as JSON and then stringify it because this too will break the signature verification.
{% endhint %}

## Constructing the Signed Content

The content to sign is created by concatenating the ID, timestamp, and payload, separated by a period (`.`). In code, it would look something like this:

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

Where `body` is the raw body of the request. The signature is highly sensitive to any changes, so even a minor modification in the body will result in a completely different signature. Therefore, you should not alter the body in any way before verifying it.

## Determining the Expected Signature

Txn uses HMAC with SHA-256 to sign its webhooks.

To calculate the expected signature, you should HMAC the `signedContent` (constructed as described above) using the Base64-decoded portion of your signing secret (the part after the `whsec_` prefix) as the key. For example, if your secret is `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw`, you should use `MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw` as the key.

Here's an example  of hot to calcialte the signature in Ruby on Rails:

```ruby
Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), Base64.decode64(secret), "#{msgId}.#{timestamp}.#{payload}")).strip
```

Here’s an example of how to calculate the signature in Node.js:

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

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

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

console.log(signature);

```

The generated signature should match one of the signatures sent in the `svix-signature` header.

The `svix-signature` header contains a list of space-delimited signatures with their corresponding version identifiers. The list usually contains one signature, but there can be multiple. For example:

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

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

**Security Note:** Use a constant-time string comparison method to compare signatures and prevent timing attacks.

## Verify Timestamp

As mentioned above, Txn includes the timestamp of the attempt in the `svix-timestamp` header. Compare this timestamp against your system's timestamp to ensure it falls within your acceptable tolerance range, helping to prevent timestamp attacks.
