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

# Verifying requests

> Check that a message came from Telzino

Each endpoint uses one of two methods.

## HMAC signature (recommended)

Headers:

| Header | Value |
| - | - |
| `x-telzino-timestamp` | Unix seconds when the request was signed |
| `x-telzino-signature` | `v1=<hex>` — HMAC-SHA256 of `"<timestamp>.<raw body>"` with your secret. During a secret rotation it lists two signatures: `v1=<new>,v1=<old>`. |

Accept the request if **any** `v1=` value matches, and reject timestamps more than 5 minutes old (replay protection). Use the raw body bytes, before any JSON parsing.

```javascript Node.js theme={null}
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyTelzino(rawBody, headers, secret) {
  const ts = Number(headers['x-telzino-timestamp']);
  if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest();
  return String(headers['x-telzino-signature'] ?? '').split(',').some((part) => {
    const hex = part.trim().replace(/^v1=/, '');
    const got = Buffer.from(hex, 'hex');
    return got.length === expected.length && timingSafeEqual(got, expected);
  });
}
```

```python Python theme={null}
import hashlib, hmac, time

def verify_telzino(raw_body: bytes, headers, secret: str) -> bool:
    h = {k.lower(): v for k, v in headers.items()}  # header names are case-insensitive
    try:
        ts = int(h.get("x-telzino-timestamp", ""))
    except ValueError:
        return False
    if abs(time.time() - ts) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest().encode()
    return any(hmac.compare_digest(p.strip().removeprefix("v1=").encode(), expected)
               for p in h.get("x-telzino-signature", "").split(","))
```

The Python sample needs Python 3.9 or later (`removeprefix`).

## Shared secret header

For tools that cannot compute an HMAC (for example GoHighLevel inbound webhooks): the secret is sent as the value of a header you name (default `X-Telzino-Secret`). Compare it in constant time. Rotating a shared secret is a **scheduled switch**: you choose when the new value takes over, so you can update your receiver first. Rotating again replaces a switch that is already scheduled, so the admin portal and dashboard ask you to confirm first.

This header has no timestamp or signature, so anyone who captures a request can send it again. De-duplicate on the `x-telzino-event-id` header.

## Other headers

`x-telzino-event-id`, `x-telzino-event-type`, `x-telzino-delivery-id` (quote it to support), `x-telzino-attempt`.


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