# Erreur WhatsApp 190 : jeton d'accès expiré, solution

URL: https://wa.genuka.com/docs/errors/190
Language: French

> Erreur WhatsApp 190 (access token expired) : quel jeton a expiré, comment le remplacer durablement, et pourquoi Genuka WA vous en dispense.

L'erreur WhatsApp 190 signifie que le jeton d'accès envoyé à la Cloud API de Meta a expiré ou n'est
plus valide. La correction consiste à obtenir un nouveau jeton, et surtout à cesser d'utiliser un
jeton utilisateur temporaire : un jeton d'utilisateur système ne tombe pas en quelques heures. Avec
Genuka WA, vous ne manipulez aucun jeton Meta.

## Que signifie l'erreur 190 ?

Meta la classe parmi les erreurs d'autorisation de la Cloud API :

> « Your access token has expired. » — solution proposée : « Get a new access token. »
> — [Meta, codes d'erreur de la Cloud API](https://developers.facebook.com/documentation/business-messaging/whatsapp/support/error-codes#authorization-errors)

La documentation générale de Graph, sur laquelle la Cloud API est construite, nomme le même code
« Access token has expired » et détaille des sous-codes
([Meta, Graph API, gestion des erreurs](https://developers.facebook.com/docs/graph-api/guides/error-handling)) :

| Sous-code Graph | Nom donné par Meta   | Ce qu'il indique                                           |
| --------------- | -------------------- | ---------------------------------------------------------- |
| `463`           | Expired              | Le jeton a expiré, a été révoqué ou n'est plus valide      |
| `467`           | Invalid Access Token | Le jeton a expiré, a été révoqué ou n'est plus valide      |
| `460`           | Password Changed     | La personne qui a généré le jeton a changé de mot de passe |
| `458`           | App Not Installed    | L'utilisateur ne s'est pas connecté à votre application    |

Ne bâtissez pas votre logique sur ces sous-codes : la page d'erreurs WhatsApp indique que
`error_subcode` est déprécié et n'est plus renvoyé à partir de la version 16.0 de Graph. Le code
`190` et le champ `details` suffisent.

## Quand l'erreur 190 apparaît-elle ?

* **Vous utilisez le jeton de la page API Setup.** Le tableau de bord d'app Meta génère un jeton
  utilisateur à chaque visite de **WhatsApp > API Setup**. Meta le réserve aux premiers tests :
  ces jetons « expirent vite », il faut en générer un nouveau toutes les quelques heures
  ([Meta, Access tokens](https://developers.facebook.com/documentation/business-messaging/whatsapp/access-tokens)).
  C'est la cause la plus fréquente d'un service qui marche le matin et échoue l'après-midi.
* **Le jeton a été invalidé.** Mot de passe changé, application retirée par l'utilisateur,
  jeton révoqué : Meta répond 190 même avant la date d'expiration.
* **Vous êtes partenaire et le jeton d'un client est mort.** Un Tech Provider reçoit, à la fin de
  l'Embedded Signup, un jeton d'utilisateur système d'intégration propre à ce client. Nous avons
  constaté qu'il cesse de fonctionner dès que le client modifie l'accès du partenaire dans son
  Business Manager : Meta répond alors 190 « Authentication Error » à chaque envoi, sur un numéro
  par ailleurs en bonne santé.

## Comment corriger l'erreur 190 ?

### Si vous appelez la Cloud API avec votre propre jeton

1. **Inspectez le jeton.** Collez-le dans le
   [débogueur de jetons de Meta](https://developers.facebook.com/tools/debug/accesstoken/) : il
   affiche la date d'expiration et les permissions. Il doit porter `whatsapp_business_management` et
   `whatsapp_business_messaging`
   ([Meta, support WhatsApp](https://developers.facebook.com/documentation/business-messaging/whatsapp/support#authentication-authorization)).
2. **Remplacez un jeton utilisateur par un jeton d'utilisateur système.** Dans les paramètres
   d'entreprise, section **System Users** : créez un utilisateur système, donnez-lui le contrôle de
   votre app, puis **Generate token** en choisissant l'app, une préférence d'expiration et les
   permissions `business_management`, `whatsapp_business_management` et
   `whatsapp_business_messaging`
   ([Meta, Access tokens](https://developers.facebook.com/documentation/business-messaging/whatsapp/access-tokens#system-user-access-tokens)).
3. **Donnez-lui accès aux comptes.** Un utilisateur système sans accès au compte WhatsApp visé ne
   déclenche plus 190 mais [200](https://wa.genuka.com/docs/errors/200) : assignez-lui le compte WhatsApp et le compte de
   messagerie dans Meta Business Suite, onglet **People** de chaque compte (même page).
4. **Stockez-le comme un secret opaque.** Meta prévient que le format des jetons peut changer : pas de
   longueur fixe en base, pas de décodage.

### Si vous passez par Genuka WA

Vous n'avez rien à régénérer. Genuka WA détient les accès à la Cloud API : chaque appel sur votre
numéro part avec le jeton d'utilisateur système de Genuka, créé sans date d'expiration, et non
avec le jeton remis par l'Embedded Signup au moment de la connexion. C'est justement pour éviter le
cas décrit plus haut que Genuka a fait ce choix.

Un `190` dans une réponse Genuka WA est donc, dans la grande majorité des cas, un incident côté
Genuka. Il revient ainsi :

```json title="409 — POST /api/v1/messages"
{
  "error": "send_config",
  "message": "Authentication Error",
  "meta": {
    "errorClass": "config",
    "retryable": false,
    "code": 190,
    "traceId": "AbC…"
  }
}
```

Sur la création d'un template, le même refus revient en `403 meta_rejected`, avec le même objet
`meta` ([référence API](https://wa.genuka.com/docs/api#erreurs)). Dans les deux cas :

1. **Ne réessayez pas en boucle.** `retryable: false` : le même appel échouera de la même façon.
2. **Écrivez au support Genuka** avec l'en-tête `x-request-id` de la réponse et `meta.traceId`.
3. **Ne demandez pas tout de suite au client de se reconnecter.** Vérifiez d'abord
   `GET /api/v1/connections`. `disconnected` signifie que Meta a signalé le compte comme n'étant
   plus partagé avec Genuka, ou supprimé, ou que vous avez libéré le numéro vous-même. Un client
   qui a retiré l'accès le rétablit par le [lien de connexion](https://wa.genuka.com/docs/onboarding), sans nouvelle
   place ; un numéro libéré demande de nouveau une place libre ; un compte supprimé ou désactivé
   par Meta ([erreur 368](https://wa.genuka.com/docs/errors/368)) ne se rétablit pas ainsi, et l'événement
   `account_update` relayé à vos webhooks dit lequel de ces cas s'applique. S'il est toujours
   `connected`, restez-en au support Genuka.

Ne confondez pas avec les erreurs de votre propre clé API, qui ne viennent jamais de Meta :
`401 missing_bearer_token` (en-tête absent) et `401 invalid_token` (clé inconnue ou révoquée), voir
[Authentification](https://wa.genuka.com/docs/authentication).

**Node.js**

```ts
const response = await fetch("https://wa.genuka.com/api/v1/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.GENUKA_WA_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    connectionId: "con_1",
    to: "+237690000001",
    template: { name: "confirmation_commande", language: "fr", variables: ["Awa", "CMD-1042"] },
  }),
});
const json = await response.json();

if (response.status === 401) {
  // Votre clé Genuka : absente, inconnue ou révoquée. Rien à voir avec Meta.
  throw new Error(`Clé API refusée : ${json.error}`);
}
if (json.meta?.code === 190 || json.meta?.code === 0) {
  // Jeton Meta refusé : côté Genuka. On s'arrête et on alerte, sans réessayer.
  console.error("Jeton Meta refusé", {
    requestId: response.headers.get("x-request-id"),
    traceId: json.meta.traceId,
  });
}
```

**Python**

```python
import os
import requests

response = requests.post(
    "https://wa.genuka.com/api/v1/messages",
    headers={"Authorization": f"Bearer {os.environ['GENUKA_WA_API_KEY']}"},
    json={
        "connectionId": "con_1",
        "to": "+237690000001",
        "template": {"name": "confirmation_commande", "language": "fr", "variables": ["Awa", "CMD-1042"]},
    },
    timeout=30,
)
body = response.json()

if response.status_code == 401:
    raise RuntimeError(f"Clé API refusée : {body['error']}")  # votre clé Genuka, pas Meta
meta = body.get("meta") or {}
if meta.get("code") in (0, 190):
    # Jeton Meta refusé : côté Genuka. Alerter, ne pas réessayer.
    print("Jeton Meta refusé", response.headers.get("x-request-id"), meta.get("traceId"))
```

**PHP**

```php
<?php
$ch = curl_init("https://wa.genuka.com/api/v1/messages");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HEADER => true,
    CURLOPT_USERAGENT => "acme-crm/1.0 (+https://example.com)",
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("GENUKA_WA_API_KEY"),
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "connectionId" => "con_1",
        "to" => "+237690000001",
        "template" => ["name" => "confirmation_commande", "language" => "fr", "variables" => ["Awa", "CMD-1042"]],
    ]),
]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
curl_close($ch);

$body = json_decode(substr($raw, $headerSize), true);
preg_match('/x-request-id:\s*(\S+)/i', substr($raw, 0, $headerSize), $requestId);

if ($status === 401) {
    throw new RuntimeException("Clé API refusée : " . $body["error"]); // votre clé Genuka
}
if (in_array($body["meta"]["code"] ?? null, [0, 190], true)) {
    error_log("Jeton Meta refusé, request " . ($requestId[1] ?? "?") . ", trace " . ($body["meta"]["traceId"] ?? "?"));
}
```

**curl**

```bash
curl -i -X POST https://wa.genuka.com/api/v1/messages \
  -H "Authorization: Bearer $GENUKA_WA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "connectionId": "con_1",
    "to": "+237690000001",
    "template": { "name": "confirmation_commande", "language": "fr", "variables": ["Awa", "CMD-1042"] }
  }'
# -i affiche x-request-id : joignez-le, avec meta.traceId, à votre message au support.
```

## Comment éviter l'erreur 190 ?

* **Jamais de jeton API Setup en production.** Il est fait pour envoyer un premier message de
  test, pas pour un service qui tourne la nuit.
* **Un jeton d'utilisateur système par environnement**, avec seulement les permissions WhatsApp
  utiles, stocké dans votre gestionnaire de secrets.
* **Surveillez les retraits d'accès.** Si vous êtes partenaire, le webhook `account_update` signale
  `PARTNER_APP_UNINSTALLED` quand un client désautorise ou désinstalle votre app, et
  `PARTNER_REMOVED` quand un compte n'est plus partagé avec vous
  ([Meta, webhook account\_update](https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/reference/account_update)).
  Genuka WA relaie ces événements sur vos [webhooks](https://wa.genuka.com/docs/webhooks) (événement `account_update`),
  mais des deux, seul `PARTNER_REMOVED` fait passer le numéro en `disconnected` : après un
  `PARTNER_APP_UNINSTALLED`, il reste `connected`.
* **Alertez sur la classe, pas sur le texte.** Chez Genuka WA, `meta.errorClass: "config"` regroupe
  jeton, permission et enregistrement : un humain doit regarder, aucun réessai ne réglera rien.

## Quels codes sont liés ?

* [0](https://wa.genuka.com/docs/errors/0) : Meta n'a pas pu authentifier l'utilisateur de l'app, même remède.
* [200](https://wa.genuka.com/docs/errors/200) : le jeton est valide mais n'a pas accès au compte ou à la permission.
* [10](https://wa.genuka.com/docs/errors/10) : permission non accordée ou retirée.
* [3](https://wa.genuka.com/docs/errors/3) : capacité ou permission manquante pour cet endpoint.

## FAQ

### Quelle est la durée de vie d'un jeton WhatsApp Cloud API ?

Elle dépend du type. Un jeton utilisateur, comme celui de la page API Setup, expire en quelques
heures. Un jeton d'utilisateur système est durable, et vous choisissez sa préférence d'expiration
au moment de le générer.

### Faut-il réessayer un envoi refusé en 190 ?

Pas avant d'avoir changé de jeton. Le même jeton produira la même erreur : réessayer ne fait que
retarder l'alerte.

### Mon client doit-il se reconnecter après une erreur 190 sur Genuka WA ?

Seulement si son numéro apparaît `disconnected` dans `GET /api/v1/connections` parce qu'il a
retiré l'accès de Genuka : l'événement `account_update` relayé à vos webhooks le confirme. Un
compte supprimé ou désactivé par Meta ne se rétablit pas par une reconnexion. Si le numéro est
toujours `connected`, écrivez au support Genuka avec `x-request-id` et `meta.traceId`.

### Dois-je régénérer ma clé API Genuka ?

Non. Votre clé `pk_live_…` authentifie vos appels auprès de Genuka WA, pas auprès de Meta. Une clé
refusée produit un `401` de Genuka, jamais un `190`.

## Sources

* [Meta — Error codes](https://developers.facebook.com/documentation/business-messaging/whatsapp/support/error-codes)
* [Meta — Access tokens](https://developers.facebook.com/documentation/business-messaging/whatsapp/access-tokens)
* [Meta — Graph API, Handling errors](https://developers.facebook.com/docs/graph-api/guides/error-handling)
* [Meta — WhatsApp support, authentication and authorization errors](https://developers.facebook.com/documentation/business-messaging/whatsapp/support#authentication-authorization)
* [Meta — account\_update webhook](https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/reference/account_update)
