# Authentication

Every key and token Texa uses, where you get it, where it may live, and what it reaches.


Texa has a few kinds of credential because they live in different places: your server, a web page,
an app, a webhook. Use the one meant for where your code runs.

## At a glance

| Credential | Looks like | Where you get it | Where it may live | Reaches |
|---|---|---|---|---|
| Texa API key, sandbox | `texa_test_…` | Texa AI → Developers → API keys | your server only | your sandbox |
| Texa API key, live | `texa_live_…` | the same | your server only | your live account |
| Site key | `pk_live_…`, `pk_test_…` | Texa AI → Developers → Website and app | a web page or app | visitor chats, only from your listed websites |
| Widget session token | `wst_…` | your server, `POST /v1/widget-sessions` | the page or app, for one customer | that customer's conversations, for up to a few hours |
| Voice session token | one-time | your server, `POST /v1/conversations/{id}/voice-sessions` | the app, once | one voice session |
| Texa Desk access token | a long random string | Texa Desk → Profile settings → Access token | your server | the Texa Desk API, as that person |
| Agent bot token | a long random string | when you create an agent bot | your bot's server | the conversations of the bot's inboxes |
| Live chat HMAC key | per website inbox | the inbox → Configuration → Identity validation | your server | signs `identifier_hash` for live chat |
| Webhook signing secret | per endpoint | when you add the endpoint | your server | checks that a request came from Texa |
| Platform app token | a long random string | a dedicated instance's super admin | your server | the [Platform API](/api-reference/platform), dedicated instances only |

## Texa API keys

Send the key as a bearer token on every request:

```http
Authorization: Bearer texa_live_...
```

- A key is shown **once**, when you make it. Texa keeps only a hash of it.
- **Revoke** a key in Texa AI → Developers → API keys. It stops working at once.
- Make **one key per system** (your backend, your WhatsApp bridge, a test laptop): the request log
  shows which key did what, and revoking one leaves the others working.
- A key never belongs in an app or a web page. For those, your server makes a
  [widget session](/guides/website-and-app#2-signed-in-customers-your-server-says-who-they-are)
  or a [voice session](/guides/voice).

## Site keys and widget sessions

A **site key** is safe to put in a web page: it only opens *visitor* chats, only from the websites
you list under **Websites your widget runs on** (the sandbox site key works anywhere), and at most
20 an hour per address. A visitor is never treated as verified.

For a signed-in customer, **your server** calls `POST /v1/widget-sessions` with its own key and the
customer's reference, and hands the page the `token`. The token reaches only that customer's
conversations, and expires (`ttl_s`, one hour by default).

## Texa Desk access tokens

Every person in Texa Desk has an access token under **Profile settings → Access token**. Send it in
the `api_access_token` header:

```bash
curl https://desk.texadesk.com/api/v1/accounts/1/conversations \
  -H "api_access_token: $TEXA_DESK_TOKEN"
```

The token acts as that person, with their role's permissions and only the inboxes and teams they
see. For an integration, add a dedicated person (an administrator or a custom role with only what
the integration needs) rather than using someone's own token, and reset it from their profile if it
leaks.

**Agent bots** have their own token, sent the same way, which works only on the conversations of the
inboxes the bot is connected to. [Agent bots](/desk/agent-bots)

## The Client API

The [Client API](/api-reference/client) builds a customer-facing chat on an API inbox. It needs no
secret: an inbox identifier (from the inbox's settings) and a contact identifier (returned when you
create the contact). Treat the contact identifier as that customer's own secret: whoever has it can
read their conversations. Turn on **identity validation** on the inbox so only your server can
create contacts for known users.

## Checking what Texa sends you

Everything Texa sends your server is signed with HMAC-SHA256: Texa API webhooks, action requests to
your server, Texa Desk webhooks. Check the signature over the raw body and reject old timestamps.

<!-- tabs -->
```python Python
import hashlib, hmac, time

def from_texa(secret: str, timestamp: str, raw_body: bytes, signature: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:
        return False
    expected = "sha256=" + hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body,
                                    hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
```
```js Node
import crypto from "node:crypto";

export function fromTexa(secret, timestamp, rawBody, signature) {
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = "sha256=" + crypto.createHmac("sha256", secret)
    .update(`${timestamp}.`).update(rawBody).digest("hex");
  return signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
```
```php PHP
function from_texa(string $secret, string $timestamp, string $rawBody, string $signature): bool {
    if (abs(time() - (int) $timestamp) > 300) return false;
    $expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
    return hash_equals($expected, $signature);
}
```
<!-- /tabs -->

| Sent by | Timestamp header | Signature header |
|---|---|---|
| Texa API webhooks and action requests | `X-Texa-Timestamp` | `X-Texa-Signature` |
| Texa Desk webhooks | `X-Chatwoot-Timestamp` | `X-Chatwoot-Signature` |

Texa Desk's webhook headers keep the names of the open-source helpdesk it is built on, so existing
integrations keep working. Every SDK has a webhook check built in.
