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

# Verify requests from Tars

> Check signed requests using the raw body, request method, request target and the correct signing secret.

## Choose the signing secret

| Request | Secret |
| - | - |
| Event webhook | The destination's signing secret |
| Chat or voice agent request | The organization's Request signing secret |
| Inbound call check | The organization's Request signing secret |
| SIEM HTTPS delivery | The audit export destination's secret |

Identity verification uses a separate secret and [identity recipe](https://hellotars.com/docs/developer/embed/identity-signing.md).

## Read the signature

X-Tars-Signature contains t, v1 and, during rotation, v2. t is milliseconds since the epoch.

The HMAC-SHA256 input joins these values with newline characters, in this order:

1. The timestamp t.
2. The uppercase HTTP method.
3. The exact request path and query string.
4. The raw request body.

Keep the original body bytes. Do not parse and serialize JSON before checking. Keep the method, path and query unchanged.

## Verification recipe

Reject timestamps more than five minutes from your clock. Compare signatures using constant-time equality.

```javascript theme={null}
import crypto from "node:crypto"

// header: the X-Tars-Signature value, e.g. "t=1760885331000,v1=5d1f…"
// rawBody: the body exactly as received, before parsing
export function isFromTars({ header, method, pathWithQuery, rawBody, secret }) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")))
  if (Math.abs(Date.now() - Number(parts.t)) > 5 * 60 * 1000) return false
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}\n${method}\n${pathWithQuery}\n${rawBody}`)
    .digest("hex")
  return [parts.v1, parts.v2].some(
    (sig) => sig?.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
  )
}
```

## Rotation

v1 uses the current secret. During the 24-hour overlap, v2 uses the previous secret.

Accept a valid signature from either secret while replacing your stored secret. Another rotation is refused until the overlap ends.

## Other request headers

| Header | Purpose |
| - | - |
| X-Tars-Event-Id | Stable event ID for deduplication |
| X-Tars-Request-Id | Request correlation ID |
| X-Tars-Conversation-Id | Related conversation, when available |
| X-Tars-Call-Id | Related call, when available |
| X-Tars-Delivery-Id | Delivery correlation ID |

Only headers applicable to the request are included. These headers do not replace signature verification.


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