# Erreur WhatsApp 4 : limite d'appels de l'app, solution

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

> Erreur WhatsApp 4 (API Too Many Calls) : l'application a atteint sa limite d'appels à l'API de Meta. Différence avec 80007 et 130429, et comment réessayer.

L'erreur WhatsApp 4 signifie que l'application qui appelle l'API de Meta a atteint sa limite
d'appels. C'est une limitation temporaire : rien n'est cassé, il faut attendre et réduire le volume
de requêtes. Elle vise l'application dans son ensemble, et non un numéro ou un compte WhatsApp, ce
qui la distingue des erreurs 80007 et 130429.

## Que signifie l'erreur 4 ?

Meta la range parmi les erreurs de limitation de débit (*throttling errors*) de la Cloud API :

> « The app has reached its API call rate limit. »
> — [Meta, codes d'erreur de la Cloud API](https://developers.facebook.com/documentation/business-messaging/whatsapp/support/error-codes#throttling-errors)

La solution proposée : ouvrir l'app dans le tableau de bord d'app, section **Application Rate
Limit**, vérifier que la limite est atteinte, puis réessayer plus tard ou réduire la fréquence et
le nombre de requêtes. La documentation de Graph appelle ce code « API Too Many Calls », un
problème temporaire de limitation : « Wait and retry the operation, or examine your API request
volume » ([Meta, Graph API, gestion des erreurs](https://developers.facebook.com/docs/graph-api/guides/error-handling)).
Sa page sur les limites précise que le code 4 indique que l'app dont le jeton est utilisé a atteint
sa limite ([Meta, Rate limits](https://developers.facebook.com/docs/graph-api/overview/rate-limiting)).

### Quatre limites, quatre codes

| Code                          | Ce qui est compté                                                                                                           | Portée                               |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| **4**                         | Les appels API de l'application                                                                                             | L'application entière                |
| [80007](https://wa.genuka.com/docs/errors/80007)   | Les appels d'une app sur un compte WhatsApp : 200 par heure par défaut, 5 000 sur un compte actif avec un numéro enregistré | Une app et un compte WhatsApp        |
| [130429](https://wa.genuka.com/docs/errors/130429) | Les messages par seconde : 80 par défaut                                                                                    | Un numéro                            |
| [131056](https://wa.genuka.com/docs/errors/131056) | Les messages vers un même destinataire : un toutes les 6 secondes                                                           | Une paire expéditeur et destinataire |

Les chiffres viennent de la présentation de la plateforme
([Meta, About the platform](https://developers.facebook.com/documentation/business-messaging/whatsapp/about-the-platform#rate-limits)).

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

* **Une rafale d'appels qui ne sont pas des envois** : lister les templates en boucle, relire la
  santé de chaque numéro à chaque affichage d'un tableau de bord, téléverser des médias à la chaîne.
* **Plusieurs services sur la même app** : un worker de campagne, un back-office et un script de
  synchronisation qui partagent le même jeton additionnent leurs appels.
* **Une boucle de réessais sans délai** : chaque échec relance immédiatement un appel, et la limite
  s'éloigne au lieu de se rapprocher.

### Où la voir dans Genuka WA ?

Avec Genuka WA, l'application Meta qui appelle est celle de Genuka, Tech Provider : la limite
d'application ne dépend pas que de votre volume. Genuka range le code 4 dans la classe
`retryable`.

| Canal                    | Ce que vous recevez                                                                                                                                    |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `POST /api/v1/messages`  | `400`, `"error": "send_retryable"`, `meta.code: 4`, `meta.retryable: true`                                                                             |
| Campagne                 | Chaque destinataire est tenté jusqu'à trois fois au total, soit deux nouvelles tentatives, après 0,25 à 0,5 s puis 0,5 à 1 s, avant de passer `failed` |
| `POST /api/v1/templates` | `422`, `"error": "meta_rejected"`, `meta.retryable: true` si Meta a répondu par un 4xx ; `502` s'il a répondu 429 ou 5xx                               |

Ce `502` arrive sans corps JSON : notre CDN remplace la réponse par la seule ligne
`error code: 502` ([référence API](https://wa.genuka.com/docs/api)). Traitez-le comme un échec passager, sans chercher
`meta` dedans.

Genuka limite aussi de lui-même les rafales qu'il pourrait provoquer pour vous :
`GET /api/v1/numbers?refresh=true` refuse l'appel (`400 too_many_connections`) au-delà de 25
numéros, restreignez-le avec `?companyId=` ; et une actualisation de templates traite les comptes
WhatsApp cinq par cinq.

## Comment corriger l'erreur 4 ?

1. **Réessayez avec un délai croissant, seulement si `meta.retryable` vaut `true`** ou sur un `5xx`
   sans corps JSON. Un envoi unitaire n'est pas réessayé par Genuka WA à votre place : c'est à votre
   code d'attendre, en doublant le délai à chaque tentative et en ajoutant un peu d'aléa pour que vos
   workers ne repartent pas ensemble.

   Pour un envoi, ne réessayez que si `meta.code` est présent (4, 80007, 130429…). Un délai dépassé
   entre Genuka et Meta est lui aussi marqué `retryable`, sans `meta.code`, alors que Meta a peut-être
   déjà accepté le message ; l'API n'a pas de clé d'idempotence, et le renvoyer peut le doubler.

2. **Supprimez les appels de lecture inutiles.** `GET /api/v1/numbers` sans `refresh=true` renvoie
   l'état enregistré, tenu à jour par les webhooks de Meta. Les statuts de templates arrivent par
   [webhook](https://wa.genuka.com/docs/webhooks) ; l'actualisation `POST /api/v1/templates/sync` est un chemin de
   réparation, une fois par heure suffit largement ([référence API](https://wa.genuka.com/docs/api#templates)).

3. **Si l'erreur persiste au-delà d'une heure**, écrivez au support Genuka avec l'en-tête
   `x-request-id` et `meta.traceId` : les limites de Graph se comptent sur une heure glissante.

Si vous appelez la Cloud API avec votre propre app, le tableau de bord d'app affiche le
pourcentage d'utilisation de la limite d'application et le nombre d'utilisateurs limités
([Meta, Rate limits](https://developers.facebook.com/docs/graph-api/overview/rate-limiting)).

**Node.js**

```ts
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

export async function callGenuka(path: string, body: unknown, maxAttempts = 5) {
  for (let attempt = 1; ; attempt++) {
    const response = await fetch(`https://wa.genuka.com/api/v1${path}`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.GENUKA_WA_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });
    // Un 5xx arrive sans JSON : notre CDN le remplace par « error code: 502 ».
    const json = await response.json().catch(() => null);
    if (response.ok) return json.data;

    // Un envoi n'est relancé que sur un code de Meta (4, 80007, 130429…) : après un délai
    // dépassé, Meta a peut-être déjà accepté le message, et le renvoyer le doublerait.
    const retryable =
      path === "/messages"
        ? json?.meta?.retryable === true && json.meta.code !== undefined
        : response.status >= 500 || json?.meta?.retryable === true;
    if (!retryable || attempt >= maxAttempts) {
      throw new Error(`${response.status} ${json?.error ?? "sans corps JSON"} (Meta ${json?.meta?.code ?? "-"})`);
    }
    const base = Math.min(30_000, 1_000 * 2 ** (attempt - 1));
    await sleep(base / 2 + Math.random() * (base / 2));
  }
}
```

**Python**

```python
import os
import random
import time
import requests

def call_genuka(path: str, body: dict, max_attempts: int = 5) -> dict:
    for attempt in range(1, max_attempts + 1):
        response = requests.post(
            f"https://wa.genuka.com/api/v1{path}",
            headers={"Authorization": f"Bearer {os.environ['GENUKA_WA_API_KEY']}"},
            json=body,
            timeout=30,
        )
        try:
            payload = response.json()
        except ValueError:
            # Un 5xx arrive sans JSON : notre CDN le remplace par « error code: 502 ».
            payload = {}
        if response.ok:
            return payload["data"]
        meta = payload.get("meta") or {}
        # Un envoi n'est relancé que sur un code de Meta (4, 80007, 130429…) : après un délai
        # dépassé, Meta a peut-être déjà accepté le message, et le renvoyer le doublerait.
        if path == "/messages":
            retryable = bool(meta.get("retryable")) and meta.get("code") is not None
        else:
            retryable = response.status_code >= 500 or bool(meta.get("retryable"))
        if not retryable or attempt == max_attempts:
            raise RuntimeError(f"{response.status_code} {payload.get('error', 'sans corps JSON')} (Meta {meta.get('code')})")
        base = min(30.0, 2 ** (attempt - 1))
        time.sleep(base / 2 + random.random() * base / 2)
```

**PHP**

```php
<?php
function callGenuka(string $path, array $body, int $maxAttempts = 5): array
{
    for ($attempt = 1; ; $attempt++) {
        $ch = curl_init("https://wa.genuka.com/api/v1" . $path);
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => 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($body),
        ]);
        $raw = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); // 0 si la requête n'a pas abouti
        curl_close($ch);
        // Un 5xx arrive sans JSON : notre CDN le remplace par « error code: 502 ».
        $json = is_string($raw) ? json_decode($raw, true) : null;
        if (!is_array($json)) $json = [];

        if ($status >= 200 && $status < 300) return $json["data"];
        // Un envoi n'est relancé que sur un code de Meta (4, 80007, 130429…) : après un délai
        // dépassé, Meta a peut-être déjà accepté le message, et le renvoyer le doublerait.
        $meta = $json["meta"] ?? [];
        $retryable = $path === "/messages"
            ? !empty($meta["retryable"]) && isset($meta["code"])
            : $status === 0 || $status >= 500 || !empty($meta["retryable"]);
        if (!$retryable || $attempt >= $maxAttempts) {
            throw new RuntimeException("$status " . ($json["error"] ?? "sans corps JSON") . " (Meta " . ($meta["code"] ?? "-") . ")");
        }
        $base = min(30.0, 2 ** ($attempt - 1));
        usleep((int) (($base / 2 + mt_rand() / mt_getrandmax() * $base / 2) * 1_000_000));
    }
}
```

**curl**

```bash
# Une seule tentative : lisez meta.retryable avant de décider de recommencer.
curl -s -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", "text": "Votre colis est parti." }' \
  | jq '{error, code: .meta.code, retryable: .meta.retryable}'
```

## Comment éviter l'erreur 4 ?

* **Préférez les webhooks au polling.** Statuts de messages, revues de templates et changements de
  qualité arrivent d'eux-mêmes ; les redemander en boucle consomme la limite pour rien.
* **Passez les envois de masse par une campagne.** Genuka cadence l'envoi au débit du numéro et
  gère les réessais destinataire par destinataire — voir [campagnes par API](https://wa.genuka.com/docs/guides/campaigns-api).
* **Ne réessayez jamais à intervalle fixe.** Trois essais à une seconde d'écart renvoient la même
  rafale au même moment.

## Quels codes sont liés ?

* [80007](https://wa.genuka.com/docs/errors/80007) : la limite d'appels d'une app sur un compte WhatsApp.
* [130429](https://wa.genuka.com/docs/errors/130429) : le débit de messages par seconde d'un numéro.
* [131056](https://wa.genuka.com/docs/errors/131056) : trop de messages vers le même destinataire en peu de temps.

## FAQ

### L'erreur 4 vient-elle de ma clé API Genuka ?

Non. C'est un code de Meta, relayé dans `meta.code`. Il concerne l'application Meta qui fait
l'appel, celle de Genuka, et non votre clé.

### Combien de temps faut-il attendre ?

Meta ne donne pas de durée fixe pour le code 4 : les limites de Graph se calculent sur une heure
glissante. Commencez par un délai croissant, d'une à trente secondes, et réduisez vos appels ; si
l'erreur dure plus d'une heure, prévenez le support.

### Un message refusé avec le code 4 est-il facturé ?

Il n'a pas été accepté par Meta, il n'y a donc pas de message à facturer. Côté Genuka WA, seuls les
messages acceptés par Meta comptent dans le quota de votre abonnement.

## Sources

* [Meta — Error codes](https://developers.facebook.com/documentation/business-messaging/whatsapp/support/error-codes)
* [Meta — Graph API, Handling errors](https://developers.facebook.com/docs/graph-api/guides/error-handling)
* [Meta — Graph API, Rate limits](https://developers.facebook.com/docs/graph-api/overview/rate-limiting)
* [Meta — About the platform, rate limits](https://developers.facebook.com/documentation/business-messaging/whatsapp/about-the-platform#rate-limits)
