> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.pivotal.app/llms.txt.
> For full documentation content, see https://docs.pivotal.app/llms-full.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.pivotal.app/_mcp/server.

# Signature verification

Pivotal signs every delivery with the endpoint's signing secret. The Svix SDK handles verification for you; this page documents what it does so you can implement it manually if you want.

HEADERS ON EVERY REQUEST

| Header           | Example                                | Notes                                         |
| ---------------- | -------------------------------------- | --------------------------------------------- |
| `svix-id`        | `msg_2nQv8c9aZmYHKr1...`               | Delivery id. Different from `event.id`.       |
| `svix-timestamp` | `1748286120`                           | Unix seconds. Reject if older than 5 minutes. |
| `svix-signature` | `v1,EXample...,v1,EXampleSecondary...` | Space-separated `version,signature` pairs.    |

VERIFICATION RECIPE

```typescript title="manual-verify.ts"
import crypto from "node:crypto";

function verify(secret: string, id: string, ts: string, sig: string, body: string) {
  // signing secret is base64; the value after "whsec_"
  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const toSign = `${id}.${ts}.${body}`;
  const expected = crypto.createHmac("sha256", key).update(toSign).digest("base64");

  // sig header can hold multiple signatures (rotation)
  const ours = sig.split(" ").some((pair) => pair.split(",")[1] === expected);
  if (!ours) throw new Error("bad signature");

  if (Math.abs(Math.floor(Date.now() / 1000) - Number(ts)) > 300) {
    throw new Error("timestamp too old");
  }
}
```

SECRET ROTATION

The Webhooks dashboard lets you rotate the signing secret with a 24-hour overlap. Deliveries during the overlap window carry two signatures so your old key keeps validating while you deploy the new one. The `svix-signature` header lists both, separated by a space — see the recipe above for how to handle multiple candidates.