> Toden documentation. For the full index fetch /llms.txt. Every page is available as Markdown by appending .md to its URL. Identity verification uses /api/kyc, Risk Intelligence uses /api/v1; they are separate contracts with different error shapes. Never invent an endpoint, field or enum value: read the API reference page for the operation first.

# Authentication

> Verify V2 signatures, reject replays and rotate endpoint secrets safely.

Verify every delivery before parsing or acting on its JSON. Toden signs the exact request bytes with the secret assigned to that endpoint.

## Signed headers

| Header                    | Example                 | Required handling                                                                                           |
| ------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------- |
| `X-Myaza-Signature-V2`    | `t=1786441200,v1=8df1…` | Parse the Unix timestamp and every `v1` candidate. Multiple `v1` values are present during secret rotation. |
| `X-Myaza-Timestamp`       | `1786441200`            | Same signed timestamp, exposed separately for diagnostics.                                                  |
| `X-Myaza-Replay-Window`   | `300`                   | Endpoint replay window in seconds. Reject signatures outside it.                                            |
| `X-Myaza-Event`           | `risk.signal.created`   | Event type for routing. The signed JSON remains authoritative.                                              |
| `X-Myaza-Event-Id`        | `evt_…`                 | Stable business-deduplication key.                                                                          |
| `X-Myaza-Delivery`        | `del_…`                 | Endpoint delivery record ID.                                                                                |
| `X-Myaza-Correlation-Id`  | `corr_…`                | Optional end-to-end correlation value when the producer supplied one.                                       |
| `X-Myaza-Webhook-Version` | `v2`                    | Endpoint body contract.                                                                                     |
| `X-Myaza-Signature`       | `sha256=…`              | Exact-body compatibility signature. New integrations should verify V2.                                      |

## Verification algorithm

1. Read the raw HTTP request body as bytes.
2. Parse `t` and all `v1` values from `X-Myaza-Signature-V2`.
3. Reject a missing, invalid, too-old or implausibly future timestamp.
4. Compute lowercase hex `HMAC-SHA256("<t>.<rawBody>", endpointSecret)`.
5. Compare the expected value with every supplied `v1` using a constant-time comparison.
6. Only after a match, parse the JSON, deduplicate on `id`, enqueue it durably and return `2xx`.

Do not re-serialise parsed JSON before verification. Whitespace and property order change the signed bytes.

## Node.js example

```js
import crypto from "node:crypto";
import express from "express";

const app = express();

app.post("/webhooks/myaza", express.raw({ type: "application/json" }), (req, res) => {
  const parts = String(req.header("X-Myaza-Signature-V2") || "").split(",");
  const timestamp = parts.find((part) => part.startsWith("t="))?.slice(2);
  const candidates = parts
    .filter((part) => part.startsWith("v1="))
    .map((part) => part.slice(3));

  const signedAt = Number(timestamp);
  if (!Number.isInteger(signedAt) || signedAt <= 0 || Math.abs(Date.now() / 1000 - signedAt) > 300) {
    return res.status(401).send("Expired signature");
  }

  const expected = crypto
    .createHmac("sha256", process.env.TODEN_WEBHOOK_SECRET)
    .update(`${timestamp}.`)
    .update(req.body)
    .digest("hex");

  const valid = candidates.some((candidate) =>
    candidate.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(candidate), Buffer.from(expected))
  );
  if (!valid) return res.status(401).send("Invalid signature");

  const event = JSON.parse(req.body.toString("utf8"));
  // Persist event.id before dispatching asynchronous business work.
  res.status(200).send("OK");
});
```

## Secret rotation

During the configured grace period Toden signs with the active secret and every unexpired grace secret. The V2 header therefore carries more than one `v1` value. Keep both endpoint secrets available and accept the request if either expected signature matches. Remove the old secret only after its dashboard grace period ends.

## Failure responses

Return `401` for missing, stale or invalid signatures. Return `2xx` only after the event is durably accepted. Other non-`2xx` responses and timeouts are retried according to the [delivery schedule](/integrate/webhooks/overview#retries).