> ## Documentation Index
> Fetch the complete documentation index at: https://storekit.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Verifying Signatures

> Secure your webhooks by verifying the HMAC signature on every request. Confirm payloads come from storekit and have not been tampered with in transit.

Verifying webhook signatures is crucial for security. It ensures that the webhook actually came from storekit and hasn't been tampered with.

## Why Verify?

Since your webhook endpoint is publicly accessible, anyone could send requests to it pretending to be storekit. Signature verification prevents:

* **Spoofed requests**: Attackers sending fake webhooks
* **Replay attacks**: Old webhooks being resent maliciously
* **Data tampering**: Modification of webhook payloads in transit

## Using Svix Libraries (Recommended)

We recommend using the official Svix libraries for verification, which handle all the complexity for you:

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Webhook } from 'svix';

  const wh = new Webhook(process.env.WEBHOOK_SECRET);

  try {
    const payload = wh.verify(rawBody, headers);
    // Process the verified payload
  } catch (err) {
    // Signature verification failed
    return res.status(400).send('Invalid signature');
  }
  ```

  ```python Python theme={null}
  from svix.webhooks import Webhook

  wh = Webhook(os.environ['WEBHOOK_SECRET'])

  try:
      payload = wh.verify(raw_body, headers)
      # Process the verified payload
  except Exception as e:
      # Signature verification failed
      return Response(status=400)
  ```

  ```go Go theme={null}
  import svix "github.com/svix/svix-webhooks/go"

  wh, _ := svix.NewWebhook(os.Getenv("WEBHOOK_SECRET"))

  err := wh.Verify([]byte(rawBody), headers)
  if err != nil {
      // Signature verification failed
      return
  }
  ```
</CodeGroup>

<Warning>
  Always use the raw request body for verification. If you parse the JSON first and then stringify it, the signature will not match due to potential formatting differences.
</Warning>

## Manual Verification

If you prefer to verify signatures manually without using the Svix library, follow the steps below.

### Signature Headers

Each webhook includes these headers for verification:

| Header           | Description                                 |
| ---------------- | ------------------------------------------- |
| `svix-id`        | Unique message identifier                   |
| `svix-timestamp` | Unix timestamp of when the message was sent |
| `svix-signature` | The signature(s) to verify against          |

## Verification Steps

### 1. Extract the Headers

```javascript theme={null}
const svixId = request.headers['svix-id'];
const svixTimestamp = request.headers['svix-timestamp'];
const svixSignature = request.headers['svix-signature'];
```

### 2. Verify Timestamp (Prevent Replay Attacks)

Reject webhooks with timestamps older than 5 minutes:

```javascript theme={null}
const tolerance = 5 * 60; // 5 minutes in seconds
const now = Math.floor(Date.now() / 1000);
const timestamp = parseInt(svixTimestamp, 10);

if (Math.abs(now - timestamp) > tolerance) {
  throw new Error('Webhook timestamp too old');
}
```

### 3. Create the Signed Content

Concatenate the webhook ID, timestamp, and body:

```javascript theme={null}
const signedContent = `${svixId}.${svixTimestamp}.${rawBody}`;
```

### 4. Calculate Expected Signature

Use HMAC-SHA256 with your webhook secret:

```javascript theme={null}
const crypto = require('crypto');

// Your secret from the dashboard (remove the 'whsec_' prefix)
const secret = Buffer.from(secretKey.split('_')[1], 'base64');

const expectedSignature = crypto
  .createHmac('sha256', secret)
  .update(signedContent)
  .digest('base64');
```

### 5. Compare Signatures

```javascript theme={null}
const signatures = svixSignature.split(' ');
const isValid = signatures.some(sig => {
  const [version, signature] = sig.split(',');
  return version === 'v1' && signature === expectedSignature;
});

if (!isValid) {
  throw new Error('Invalid webhook signature');
}
```
