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

# Verify signatures

> Use the x-bottari-signature header to confirm a webhook request came from Bottari.

Every webhook request carries a signature. Checking it confirms that the request came from Bottari and that the body wasn't changed.

| Item | Value |
| - | - |
| Header | `x-bottari-signature: sha256=<hex>` |
| Method | HMAC-SHA256 |
| Key | The verification token (`bts_...`) |
| Message | The raw bytes of the request body |

> [!WARNING]
>
> Sign the bytes you received. Don't parse the body and serialize it again. `attempt_number` changes on retries, so the signature differs per attempt.

## Node example

This example uses only `node:http` and `node:crypto`. Without the `BOTTARI_WEBHOOK_TOKEN` environment variable, it treats the request as the verification step and logs the body.

```js theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";

const token = process.env.BOTTARI_WEBHOOK_TOKEN;

function isValidSignature(rawBody, header, secret) {
  if (typeof header !== "string") return false;
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(header);
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}

createServer((req, res) => {
  const chunks = [];
  req.on("data", (chunk) => chunks.push(chunk));
  req.on("end", () => {
    const rawBody = Buffer.concat(chunks);

    // Verification step: enter the token in the app, then set the environment variable.
    if (!token) {
      console.log(rawBody.toString());
      res.writeHead(200).end();
      return;
    }

    if (!isValidSignature(rawBody, req.headers["x-bottari-signature"], token)) {
      res.writeHead(401).end();
      return;
    }

    const event = JSON.parse(rawBody);
    console.log(event.id, event.type, event.entity.id);
    // Queue slow work and answer right away. You have 15 seconds to return a 2xx.
    res.writeHead(200).end();
  });
}).listen(3000);
```

> [!NOTE]
>
> After verification, set the environment variable so the logging code no longer runs. Delete the token from your logs.

With Express, use `express.raw({ type: "application/json" })` instead of `express.json()` so you keep the raw bytes.

## Test values

Use these to check your implementation. They are examples, not a real token.

| Item | Value |
| - | - |
| Token | `bts_EXAMPLE_TOKEN_DO_NOT_USE_aaaaaaaaaaaaaaaaaa` |
| Body | `{"id":"44444444-4444-4444-8444-444444444444","type":"page.created"}` |
| Signature | `sha256=276158c4322eef888cc3e3c241818c99deb0b5798224809790870df03f9ffbe6` |

## Signature on the verification request

The request that delivers the verification token is signed with that same token. Your server doesn't have the key yet, so it can't check this signature. A subscription only turns on when the token is entered in the app, so only someone who received the token at that address can verify it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.