# Verify signatures

> Check that a webhook request really came from Vaartix before you trust it.

Source: https://vaartix.com/docs/webhooks/verify/

Anyone can send a `POST` to your webhook URL. Every request from Vaartix is signed with your endpoint’s secret, so check the signature first and reject anything that doesn’t match.

Vaartix follows the open [Standard Webhooks](https://www.standardwebhooks.com) specification, so you can also use any Standard Webhooks library.

The signature covers the exact bytes Vaartix sent. Verify the raw request body before your framework parses it into JSON; re-serialising it can change spacing and break the check.

**Node.js (Express)**

```ts
import express from 'express';
import { verifyWebhook, WebhookVerificationError } from 'vaartix';

const app = express();

app.post('/webhooks/vaartix', express.raw({ type: 'application/json' }), (req, res) => {
  let event;
  try {
    event = verifyWebhook({
      payload: req.body, // a Buffer, thanks to express.raw()
      headers: req.headers,
      secret: process.env.VAARTIX_WEBHOOK_SECRET!,
    });
  } catch (err) {
    if (err instanceof WebhookVerificationError) return res.status(400).send('Invalid signature');
    throw err;
  }

  res.sendStatus(200);
  // Handle the event after responding, e.g. queue it.
  if (event.type === 'email.bounced') markAddressBad(event.data.recipient);
});
```

## How the signature works

1.  Take the `webhook-id` and `webhook-timestamp` headers and the raw body.
2.  Join them with dots: `{webhook-id}.{webhook-timestamp}.{body}`.
3.  Compute HMAC-SHA256 of that string. The key is your secret **without** the `whsec_` prefix, base64-decoded.
4.  Base64-encode the result and compare it, in constant time, with each `v1,` entry in the `webhook-signature` header (entries are separated by spaces).
5.  Reject requests whose timestamp is more than 5 minutes from your clock, so an intercepted request can’t be replayed later.
