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

# Identité vérifiée du widget

> Réserver le chatbot intégré aux utilisateurs authentifiés par votre application, avec un jeton signé par votre serveur.

Guide destiné aux développeurs qui intègrent le widget Moustache AI sur leur site ou leur application.

Avec l'identité vérifiée, **votre serveur** atteste de l'identité de l'utilisateur connecté en signant un jeton (JWT). Le widget échange ce jeton contre une session Moustache AI liée à cet utilisateur et à cet agent.

> L'API REST (clients, réponses, etc.) est décrite dans la page [Pour commencer](/index).

***

## Ce que l'option protège

Sur un agent **Privé** :

* **Une URL d'agent divulguée ne sert à rien** sans un jeton signé par votre serveur. Copier le snippet ou l'UUID de l'agent sur un autre site ne permet pas de discuter avec lui.
* **Chaque utilisateur ne voit que son propre historique.** La session est liée à un utilisateur (`sub`) et à un agent : elle ne donne accès à aucune autre conversation.
* **La révocation est immédiate** : une rotation de la clé, ou la désactivation de l'option, coupe toutes les sessions en cours.

## Ce qu'elle ne protège pas

* **Elle ne masque pas l'URL ni l'UUID de l'agent**, qui restent visibles dans le code source de vos pages.
* **Un agent Public reste accessible anonymement**, avec ou sans `userToken`, et l'historique d'un utilisateur n'y est pas confidentiel. Pour réserver l'agent à vos utilisateurs authentifiés, **passez-le en Privé** (voir [Mise en place sans interruption](#mise-en-place-sans-interruption)).
* **Les métadonnées d'un utilisateur sont partagées entre les agents de votre compte.** Si vous gardez un autre agent Public et que vous enrichissez vos utilisateurs via l'API (`/api/v1/customers`), ces métadonnées restent lisibles par qui connaît l'identifiant de l'utilisateur. Dans ce cas, passez aussi cet agent en Privé, ou n'y placez pas de données sensibles.
* **Elle vaut ce que vaut votre authentification** : toute personne qui obtient un jeton valide peut ouvrir une session au nom de cet utilisateur tant que le jeton n'a pas expiré. Ne délivrez de jeton qu'à l'utilisateur connecté et gardez une durée de vie courte.
* Les utilisateurs de votre entreprise connectés à Moustache AI conservent l'accès aux agents Privés, comme aujourd'hui.

## Prérequis

1. **L'option est activée sur votre compte par l'équipe Moustache AI.** Tant qu'elle ne l'est pas, la section « Identité vérifiée du widget » de la Console l'indique ; contactez-nous pour l'activer.
2. **Une clé secrète est générée** par un administrateur : Console > **Administration** > **Identité vérifiée du widget** > **Générer la clé**. La clé commence par `mwsk_`.
3. **La clé est stockée côté serveur uniquement** (variable d'environnement, coffre de secrets). Elle ne doit jamais apparaître dans du code exécuté par le navigateur, ni dans votre dépôt.

Une fois l'option activée, la fenêtre **Extrait de code Web Widget & Iframe** de chaque agent (Console > **Agents**) affiche aussi un snippet « Identité vérifiée (agents privés) ».

L'identité vérifiée fonctionne avec l'intégration par **script** (`chat.mjs`). L'intégration par iframe directe (`/chat/<uuid>?userIdentifier=…`) ne la prend pas en charge.

## Le jeton utilisateur (JWT)

Votre serveur signe un JWT par utilisateur :

| Élément | Valeur |
| - | - |
| Algorithme | `HS256` uniquement |
| Clé | La clé secrète **telle quelle** (chaîne complète, préfixe `mwsk_` inclus, en octets UTF-8). Ne la décodez pas en base64. |
| `sub` | **Obligatoire.** Identifiant stable de l'utilisateur dans votre application, 100 caractères maximum, sans espace au début ni à la fin ni caractère de contrôle. Même `sub` = même historique. |
| `exp` | **Obligatoire.** Au plus 24 h dans le futur. **Recommandé : 10 minutes.** |
| `iat` | Recommandé. |
| `aud` | Optionnel. S'il est présent, il doit être l'UUID de l'agent : le jeton ne sera alors accepté que pour cet agent. |

Une tolérance de 60 secondes est appliquée sur les dates pour absorber un léger décalage d'horloge.

Le jeton ne sert qu'au démarrage du widget (et à son renouvellement, voir plus bas) : une durée de 10 minutes suffit et limite l'intérêt d'un jeton intercepté.

Le `sub` est rattaché au même utilisateur que le `companyIdentifier` de l'API publique : un utilisateur garde un seul historique quel que soit le canal.

## Côté page

Dans le snippet copié depuis la Console, remplacez `userIdentifier` par `userToken`. Si les deux sont fournis, `userToken` l'emporte (un avertissement s'affiche dans la console du navigateur).

**Forme simple : une chaîne**, générée par votre serveur au rendu de la page.

```js theme={null}
window.initMoustacheAI({
  chatbot: 'UUID-DE-L-AGENT',
  userToken: <?= json_encode($moustacheUserToken) ?>,
})
```

**Forme recommandée : une fonction** qui renvoie une chaîne ou une `Promise<string>`. Le widget l'appelle à chaque fois qu'il a besoin d'un jeton neuf, ce qui permet le renouvellement automatique de la session.

```js theme={null}
window.initMoustacheAI({
  chatbot: 'UUID-DE-L-AGENT',
  userToken: async () => {
    const response = await fetch('/moustache/token', {
      credentials: 'same-origin',
    })
    if (!response.ok) {
      throw new Error('Jeton Moustache AI indisponible')
    }
    const { token } = await response.json()
    return token
  },
})
```

Si `userToken` est fourni et que la vérification échoue, **le widget ne s'affiche pas** et un message d'erreur est écrit dans la console du navigateur (voir [Erreurs](#erreurs)).

## Côté serveur

Les deux exemples exposent une route `/moustache/token`, réservée à l'utilisateur connecté, qui renvoie `{ "token": "…" }`. Adaptez le nom de la route et la récupération de l'utilisateur à votre application.

### Node.js ([jose](https://github.com/panva/jose))

```js theme={null}
// npm install jose
import { SignJWT } from 'jose'

const secret = new TextEncoder().encode(
  process.env.MOUSTACHE_WIDGET_IDENTITY_SECRET // la clé mwsk_… telle quelle
)

export async function createMoustacheUserToken(userId) {
  return await new SignJWT({})
    .setProtectedHeader({ alg: 'HS256' })
    .setSubject(String(userId)) // 100 caractères maximum
    .setIssuedAt()
    .setExpirationTime('10m')
    // .setAudience('UUID-DE-L-AGENT') // optionnel
    .sign(secret)
}

// Exemple Express
app.get('/moustache/token', requireLogin, async (req, res) => {
  res.set('Cache-Control', 'no-store')
  res.json({ token: await createMoustacheUserToken(req.user.id) })
})
```

### PHP ([firebase/php-jwt](https://github.com/firebase/php-jwt))

```php theme={null}
<?php
// composer require firebase/php-jwt
require __DIR__ . '/vendor/autoload.php';

use Firebase\JWT\JWT;

session_start();
if (!isset($_SESSION['user_id'])) {
    http_response_code(401);
    exit;
}

$secret = getenv('MOUSTACHE_WIDGET_IDENTITY_SECRET'); // la clé mwsk_… telle quelle
$now = time();

$token = JWT::encode([
    'sub' => (string) $_SESSION['user_id'], // 100 caractères maximum
    'iat' => $now,
    'exp' => $now + 600,                    // 10 minutes
    // 'aud' => 'UUID-DE-L-AGENT',          // optionnel
], $secret, 'HS256');

header('Content-Type: application/json');
header('Cache-Control: no-store');
echo json_encode(['token' => $token]);
```

## Mise en place sans interruption

1. **Laissez l'agent en Public** et déployez `userToken` (serveur + page). Vérifiez que le widget s'ouvre, qu'aucune erreur n'apparaît dans la console du navigateur et que chaque utilisateur retrouve son historique.
2. **Passez l'agent en Privé** (réglages de l'agent, champ **Accès**). À partir de là, seuls les widgets munis d'un jeton valide fonctionnent.

Avant l'étape 2, vérifiez que **toutes** les pages qui intègrent cet agent utilisent `userToken` : une page restée sur `userIdentifier` seul ne pourra plus afficher le widget à vos visiteurs.

## Rotation de la clé

Console > **Administration** > **Identité vérifiée du widget** > **Régénérer la clé**.

* L'ancienne clé cesse de fonctionner **immédiatement** : les jetons signés avec elle sont refusés et toutes les sessions de widget en cours sont révoquées.
* Il n'y a pas de période de recouvrement : mettez à jour la clé sur votre serveur juste après la rotation. En attendant, le widget ne s'affiche pas (erreur `invalid_signature`).
* Régénérez la clé dès que vous soupçonnez une fuite.

## Durée de session et renouvellement

* Une session de widget dure **12 heures**.
* Le widget la renouvelle automatiquement **avant d'ouvrir la fenêtre de discussion** lorsqu'il lui reste moins de 30 minutes, et **lorsqu'elle est refusée** en cours d'utilisation (expirée ou révoquée) ; la fenêtre se recharge alors avec la nouvelle session et l'historique est conservé.
* Chaque renouvellement demande un nouveau jeton à `userToken`. **Avec une fonction**, c'est transparent. **Avec une chaîne**, le widget réutilise le même jeton, qui aura expiré si vous suivez la recommandation de 10 minutes : il faudra recharger la page. Préférez la fonction pour les pages qui restent ouvertes longtemps (applications monopage, back-offices).

## Erreurs

En cas d'échec, la console du navigateur affiche :

```
Moustache AI: the user identity could not be verified (<code>). The chatbot will not be loaded.
```

L'onglet **Réseau** des outils de développement montre la requête `POST …/api/chatbots/chat-ui/<uuid>/session` et sa réponse JSON `{ "error", "code", "reason" }`.

| Statut HTTP | `<code>` affiché | Cause probable |
| - | - | - |
| 400 | `http_400` | UUID d'agent invalide, ou `userToken` absent ou vide. |
| 401 | `invalid_user_token: malformed` | Le jeton n'est pas un JWT lisible (valeur tronquée, guillemets en trop, mauvaise variable). |
| 401 | `invalid_user_token: invalid_signature` | Mauvaise clé (ancienne clé après une rotation, clé tronquée ou décodée en base64, espace en trop), algorithme autre que `HS256`, ou jeton tronqué dans sa signature. |
| 401 | `invalid_user_token: expired` | `exp` dépassé : jeton mis en cache ou réutilisé, horloge du serveur décalée. |
| 401 | `invalid_user_token: invalid_claims` | `sub` absent, vide, de plus de 100 caractères ou entouré d'espaces ; `exp` absent ou à plus de 24 h ; `aud` différent de l'UUID de l'agent. |
| 403 | `widget_identity_not_enabled` | Option non activée sur votre compte, ou aucune clé générée. |
| 404 | `http_404` | Agent inexistant, supprimé ou désactivé. |
| 429 | `http_429` | Trop d'échanges depuis la même adresse IP en peu de temps (actuellement 30 par minute). Réessayez après le délai indiqué par l'en-tête `Retry-After`. |
| 500 | `internal_error` | Erreur interne côté Moustache AI. Réessayez, puis contactez-nous si elle persiste. |

Sans requête vers Moustache AI, le code peut aussi valoir :

* `user_token_unavailable` : votre fonction `userToken` a levé une erreur ;
* `missing_user_token` : elle a renvoyé une valeur vide ;
* `network_error` : la requête a été bloquée, par exemple par une politique CSP (autorisez l'hôte Moustache AI dans `connect-src`).

Si la console indique `the widget session was refused right after it was issued`, la session a été refusée juste après sa création (par exemple une rotation de clé ou une désactivation au même moment) : vérifiez l'état de l'option et de la clé dans la Console.


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