# Messages WhatsApp

Ces quatre endpoints ouvrent l'addon WhatsApp Sender à l'API : les modèles de messages de commande validés pour la plateforme, le solde WhatsApp de la boutique, l'historique des messages envoyés aux acheteurs, et un appel qui envoie un modèle à l'acheteur d'une commande. Un message envoyé par l'API suit les mêmes règles que les messages automatiques. Il prend un message du solde à sa mise en file, et ce crédit revient quand WhatsApp ne facture pas le message : refusé, jamais délivré, ou délivré sans facturation. Un message jamais envoyé n'est jamais débité.

Le texte libre n'est pas possible. Chaque message est l'un des six modèles listés plus bas, rempli à partir de la commande (prénom de l'acheteur, numéro de commande, nom de la boutique, transporteur, point de retrait, montant), en arabe ou en français, avec le bouton « Suivre ma commande ».

## Avant de commencer[​](#avant-de-commencer "Lien direct vers Avant de commencer")

* L'addon **WhatsApp Sender** doit être activé sur la boutique (page Extensions du dashboard). Les trois endpoints de lecture fonctionnent sans lui ; l'envoi répond `403 addon_not_active`.
* Le solde se recharge depuis la page de l'addon dans le dashboard (`/dashboard/whatsapp-sender`, bouton **Recharger**). L'API lit le solde mais ne peut pas le recharger.
* Les messages ne partent que vers des numéros mobiles algériens (05, 06 ou 07). Tout autre numéro est ignoré avec `invalid_number`, sans débit.
* La clé a besoin des portées WhatsApp. Les clés créées depuis le dashboard (**Paramètres → API**, `/dashboard/api`) ont les deux. **Les portées sont figées à la création de la clé** : une clé créée avant la v1.6 ne les a pas. Créez une nouvelle clé depuis le dashboard pour utiliser ces endpoints. Une clé créée par `POST /v1/keys` n'obtient que les portées que détient la clé qui l'a créée.

| Portée          | Description                                                                                                                   |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `whatsapp:read` | Consulter les modèles de messages WhatsApp, votre solde et l'historique des messages envoyés                                  |
| `whatsapp:send` | Envoyer des messages WhatsApp à vos acheteurs au sujet de leurs commandes (chaque message est débité de votre solde WhatsApp) |

## `GET /v1/whatsapp/templates`[​](#get-v1whatsapptemplates "Lien direct vers get-v1whatsapptemplates")

Le catalogue des modèles : le texte arabe et français, des valeurs d'exemple pour chaque champ, et le statut de validation de chaque langue.

**Auth :** clé plateforme avec `whatsapp:read`.

Le statut est le dernier lu par la plateforme auprès de WhatsApp. Il est rafraîchi au plus une fois toutes les 10 minutes tant que des messages partent, et `UNKNOWN` veut dire qu'il n'a pas encore été lu. Cet appel ne contacte jamais WhatsApp lui-même. Seuls les modèles `APPROVED` sont envoyés : un envoi dans une langue dont le modèle n'est pas validé est ignoré avec `template_not_approved`, sans débit.

### Requête[​](#requête "Lien direct vers Requête")

```
curl 'https://api.dzbuild.app/v1/whatsapp/templates' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Réponse 200[​](#réponse-200 "Lien direct vers Réponse 200")

Un seul des six éléments est montré.

```
{

  "data": {

    "items": [

      {

        "key": "shipped_home",

        "name": "dz_order_shipped_home",

        "toggle": "shipped",

        "languages": {

          "ar": {

            "body": "أهلاً {{1}}، طلبك رقم {{2}} من {{3}} في الطريق مع {{4}}.\nسيصلك خلال {{5}}. سيتصل بك عامل التوصيل قبل الوصول، يرجى إبقاء هاتفك متاحاً وتجهيز المبلغ: {{6}} دج.\nاضغط على الزر لتتبع طلبك.",

            "example": ["أحمد", "1024", "متجري", "Yalidine", "يوم إلى 3 أيام", "3500"],

            "status": "APPROVED"

          },

          "fr": {

            "body": "Bonjour {{1}}, votre commande n° {{2}} chez {{3}} est en route avec {{4}}.\nLivraison prévue sous {{5}}. Le livreur vous appellera avant d'arriver : restez joignable et préparez le montant de {{6}} DA.\nAppuyez sur le bouton pour suivre votre commande.",

            "example": ["Ahmed", "1024", "Ma Boutique", "Yalidine", "1 à 3 jours", "3500"],

            "status": "APPROVED"

          }

        }

      }

    ]

  }

}
```

### Les six modèles[​](#les-six-modèles "Lien direct vers Les six modèles")

| `key`             | `toggle`          | Ce que lit l'acheteur                                                                     |
| ----------------- | ----------------- | ----------------------------------------------------------------------------------------- |
| `received`        | `received`        | Sa commande est arrivée et la boutique va l'appeler pour la confirmer.                    |
| `confirmed`       | `confirmed`       | Sa commande est confirmée et en préparation, avec le montant à payer.                     |
| `shipped_home`    | `shipped`         | Sa commande est en route vers son adresse, avec le délai de livraison réglé dans l'addon. |
| `shipped_desk`    | `shipped`         | Sa commande est en route vers un point de retrait, que le message nomme.                  |
| `delivery_failed` | `delivery_failed` | Le livreur n'a pas pu le joindre aujourd'hui et réessaiera demain.                        |
| `desk_ready`      | `desk_ready`      | Le colis l'attend au point de retrait.                                                    |

`toggle` est l'interrupteur de message automatique, dans les réglages de l'addon, dont dépend le modèle. Il ne contrôle que les messages automatiques ; un envoi par l'API l'ignore.

## `GET /v1/whatsapp/balance`[​](#get-v1whatsappbalance "Lien direct vers get-v1whatsappbalance")

Le solde, l'état de l'addon et les compteurs de messages affichés sur la page de l'addon.

**Auth :** clé plateforme avec `whatsapp:read`.

### Requête[​](#requête-1 "Lien direct vers Requête")

```
curl 'https://api.dzbuild.app/v1/whatsapp/balance' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Réponse 200[​](#réponse-200-1 "Lien direct vers Réponse 200")

```
{

  "data": {

    "balance": 412,

    "low_balance": false,

    "addon_active": true,

    "stats": {

      "sent": 12,

      "delivered": 230,

      "read": 158,

      "failed": 4,

      "free": 9,

      "used_month": 96

    }

  }

}
```

| Champ                                                         | Signification                                                                                                                                                   |
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `balance`                                                     | Messages restants dans le solde. Une boutique qui n'a jamais rechargé a `0`.                                                                                    |
| `low_balance`                                                 | `true` sous 50 messages.                                                                                                                                        |
| `addon_active`                                                | L'addon WhatsApp Sender est-il activé sur la boutique.                                                                                                          |
| `stats.sent`, `stats.delivered`, `stats.read`, `stats.failed` | Messages des 30 derniers jours, comptés selon leur statut actuel. `delivered` inclut les messages lus par l'acheteur : n'additionnez pas `delivered` et `read`. |
| `stats.free`                                                  | Messages des 30 derniers jours arrivés chez l'acheteur mais non facturés par WhatsApp. Leur crédit est revenu sur le solde.                                     |
| `stats.used_month`                                            | Messages débités du solde depuis le 1er du mois et non recrédités : ceux facturés par WhatsApp et ceux dont WhatsApp n'a pas encore signalé le résultat.        |

## `GET /v1/whatsapp/messages`[​](#get-v1whatsappmessages "Lien direct vers get-v1whatsappmessages")

Les messages de la boutique, du plus récent au plus ancien : les messages automatiques (`source` vaut `auto`) et ceux envoyés par l'API (`source` vaut `api`). Le numéro de téléphone de l'acheteur n'est jamais renvoyé.

**Auth :** clé plateforme avec `whatsapp:read`.

### Paramètres de requête[​](#paramètres-de-requête "Lien direct vers Paramètres de requête")

| Param      | Type     | Défaut | Notes                                                                                                                |
| ---------- | -------- | ------ | -------------------------------------------------------------------------------------------------------------------- |
| `order_id` | chiffres | aucun  | Seulement les messages de cette commande. Toute valeur qui n'est pas composée de chiffres renvoie `400 bad_request`. |
| `limit`    | int      | 50     | De 1 à 200.                                                                                                          |
| `cursor`   | string   | aucun  | Le `next_cursor` de la page précédente. Voir [Pagination](https://dzbuild.com/fr/fr/api-docs/pagination.md).         |

### Requête[​](#requête-2 "Lien direct vers Requête")

```
curl 'https://api.dzbuild.app/v1/whatsapp/messages?order_id=6894' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Réponse 200[​](#réponse-200-2 "Lien direct vers Réponse 200")

```
{

  "data": {

    "items": [

      {

        "id": 4181,

        "order_id": 6894,

        "event": "shipped_home",

        "source": "api",

        "status": "read",

        "language": "fr",

        "template_name": "dz_order_shipped_home",

        "error_title": null,

        "refunded": false,

        "billing": "charged",

        "created_at": "2026-09-26 10:14:03"

      },

      {

        "id": 4180,

        "order_id": 6894,

        "event": "confirmed",

        "source": "auto",

        "status": "delivered",

        "language": "ar",

        "template_name": "dz_order_confirmed",

        "error_title": null,

        "refunded": true,

        "billing": "free",

        "created_at": "2026-09-25 18:02:41"

      }

    ],

    "next_cursor": null,

    "has_more": false

  }

}
```

| Champ           | Signification                                                                                                                                                                                                              |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event`         | Pour un message automatique, l'événement de commande qui l'a déclenché (`received`, `confirmed`, `shipped`, `delivery_failed`, `desk_ready`). Pour un message API, la clé du modèle envoyé.                                |
| `source`        | `auto` ou `api`.                                                                                                                                                                                                           |
| `status`        | Voir le tableau ci-dessous.                                                                                                                                                                                                |
| `language`      | `ar` ou `fr`.                                                                                                                                                                                                              |
| `template_name` | Le nom du modèle enregistré chez WhatsApp.                                                                                                                                                                                 |
| `error_title`   | Pourquoi un message a été ignoré ou a échoué, sinon `null`.                                                                                                                                                                |
| `refunded`      | `true` une fois le crédit revenu sur le solde.                                                                                                                                                                             |
| `billing`       | `charged` une fois le message facturé par WhatsApp, `free` une fois son crédit revenu sur le solde (échec, non délivré après 30 jours, ou délivré sans facturation), `null` tant que WhatsApp n'a pas signalé le résultat. |
| `created_at`    | `YYYY-MM-DD HH:MM:SS`, heure du serveur.                                                                                                                                                                                   |

| `status`    | Signification                                                                                                                                                                                                                                                                                                                                                                                                      |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `queued`    | Payé, en attente. La plateforme l'envoie en une minute environ.                                                                                                                                                                                                                                                                                                                                                    |
| `sending`   | Envoi en cours vers WhatsApp.                                                                                                                                                                                                                                                                                                                                                                                      |
| `sent`      | Envoyé : accepté par WhatsApp.                                                                                                                                                                                                                                                                                                                                                                                     |
| `delivered` | Livré sur le téléphone de l'acheteur.                                                                                                                                                                                                                                                                                                                                                                              |
| `read`      | Lu par l'acheteur.                                                                                                                                                                                                                                                                                                                                                                                                 |
| `failed`    | Échoué : WhatsApp n'a pas pu le délivrer. Si l'envoi vers WhatsApp lui-même a échoué, le crédit revient sur le solde en quelques minutes. Si WhatsApp signale l'échec, la plateforme attend 24 heures avant de recréditer le message, car WhatsApp peut encore le signaler livré sur un autre appareil de l'acheteur, et le statut passe alors à `delivered`. `refunded` passe à `true` une fois le crédit revenu. |
| `skipped`   | Non envoyé et non débité. `error_title` donne la raison.                                                                                                                                                                                                                                                                                                                                                           |

Un message ignoré porte l'une de ces raisons dans `error_title` :

| `error_title`           | Signification                                                                    |
| ----------------------- | -------------------------------------------------------------------------------- |
| `invalid_number`        | Numéro invalide : ce n'est pas un mobile algérien.                               |
| `suppressed`            | Numéro sans WhatsApp.                                                            |
| `template_not_approved` | Modèle en attente de validation dans cette langue.                               |
| `empty_param`           | Données manquantes : il manque à la commande une valeur dont le modèle a besoin. |

## `POST /v1/orders/{id}/whatsapp`[​](#post-v1ordersidwhatsapp "Lien direct vers post-v1ordersidwhatsapp")

Met en file un modèle pour l'acheteur d'une commande et débite un message du solde. La plateforme l'envoie en une minute environ.

**Auth :** clé plateforme avec `whatsapp:send`. **Nécessite `Idempotency-Key`.**

### Corps[​](#corps "Lien direct vers Corps")

| Champ      | Type         | Requis | Notes                                                                                                                                                                                        |
| ---------- | ------------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `template` | string       | oui    | Une `key` de `GET /v1/whatsapp/templates`, ou `shipped`, qui choisit `shipped_home`, `shipped_desk` ou `desk_ready` selon le type de livraison de la commande (domicile, bureau ou retrait). |
| `language` | `ar` ou `fr` | non    | Par défaut, la langue des messages choisie dans les réglages de l'addon, ou la langue de la boutique si aucune n'a été choisie.                                                              |

### Ce que fait l'appel[​](#ce-que-fait-lappel "Lien direct vers Ce que fait l'appel")

1. Il vérifie que l'addon est activé et que la commande appartient à la boutique. Une commande d'une autre boutique répond `404`, comme une commande qui n'existe pas.
2. Il ignore les interrupteurs de messages automatiques de l'addon : vous pouvez envoyer un modèle que le marchand a désactivé pour les messages automatiques.
3. Chaque modèle ne peut être envoyé qu'une fois par commande via l'API. Un second appel répond `409 already_sent` avec l'`id` et le `status` du message précédent. Seule exception : une tentative précédente ignorée, par exemple parce que le numéro était invalide et a été corrigé depuis sur la commande. L'appel réessaie alors.
4. Il vérifie le numéro, la validation du modèle et les données de la commande. Un problème répond `422` avec la raison comme code d'erreur, enregistre un message ignoré et ne débite rien.
5. Il débite un message du solde. Un solde vide répond `402 no_credit` et n'enregistre rien.
6. Il répond `202`. Suivez le message avec `GET /v1/whatsapp/messages?order_id=` suivi de l'id de la commande. Un message refusé par WhatsApp passe à `failed` et est recrédité.

Les messages API sont comptés à part des messages automatiques. Envoyer `shipped_home` par l'API n'empêche pas le message automatique « Commande en route » pour la même commande, et le message automatique ne bloque pas l'envoi par l'API. Chacun est payé.

### Requête[​](#requête-3 "Lien direct vers Requête")

```
curl -X POST 'https://api.dzbuild.app/v1/orders/6894/whatsapp' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Content-Type: application/json" \

  -H "Idempotency-Key: wa-6894-shipped-1" \

  -d '{"template": "shipped", "language": "fr"}'
```

### Réponse 202[​](#réponse-202 "Lien direct vers Réponse 202")

`template` est la clé mise en file, avec `shipped` déjà résolu.

```
{

  "data": {

    "message_id": 4181,

    "status": "queued",

    "template": "shipped_home",

    "language": "fr"

  }

}
```

### Erreurs[​](#erreurs "Lien direct vers Erreurs")

| HTTP | Code                    | Cause                                                                                                                            |
| ---- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `bad_request`           | L'id de commande n'est pas composé de chiffres, le corps n'est pas un JSON valide, ou `Idempotency-Key` manque ou est mal formé. |
| 402  | `no_credit`             | Le solde WhatsApp est vide. Rien n'a été enregistré.                                                                             |
| 403  | `forbidden`             | « Missing scope: whatsapp<!-- -->:send<!-- --> »                                                                                 |
| 403  | `addon_not_active`      | L'addon WhatsApp Sender n'est pas activé sur la boutique.                                                                        |
| 404  | `not_found`             | Aucune commande avec cet id dans la boutique.                                                                                    |
| 409  | `already_sent`          | Ce modèle a déjà été envoyé pour cette commande via l'API.                                                                       |
| 422  | `unknown_template`      | `template` n'est ni `shipped` ni une clé du catalogue.                                                                           |
| 422  | `invalid_language`      | `language` n'est ni `ar` ni `fr`.                                                                                                |
| 422  | `invalid_number`        | Le téléphone de la commande n'est pas un mobile algérien.                                                                        |
| 422  | `suppressed`            | Le téléphone de la commande n'a pas de compte WhatsApp.                                                                          |
| 422  | `template_not_approved` | Le modèle n'est pas encore validé dans cette langue.                                                                             |
| 422  | `empty_param`           | Il manque à la commande une valeur dont le modèle a besoin.                                                                      |
| 422  | `idempotency_key_reuse` | La même `Idempotency-Key` a servi avec un autre corps ou pour une autre commande.                                                |
| 500  | `send_failed`           | Le message n'a pas pu être mis en file. Réessayez avec la même clé.                                                              |

Voici un `409 already_sent`.

```
{

  "error": {

    "code": "already_sent",

    "message": "This template was already sent for this order",

    "id": 4181,

    "status": "delivered"

  }

}
```

### Réessais et `Idempotency-Key`[​](#réessais-et-idempotency-key "Lien direct vers réessais-et-idempotency-key")

La première réponse à une clé est conservée 24 heures. Un réessai avec la même clé et le même corps renvoie cette réponse avec `Idempotency-Replay: 1`, y compris un `402`, un `403` ou un `422`. Après avoir rechargé le solde, activé l'addon ou corrigé la commande, **réessayez avec une nouvelle `Idempotency-Key`** : l'ancienne clé continue de renvoyer l'ancienne erreur. Un `202` rejoué veut dire qu'aucun second message n'a été mis en file.

La même clé avec un autre corps ou pour une autre commande répond `422 idempotency_key_reuse`. Une réponse `5xx` ou `429` n'est jamais conservée : réessayez-la avec la même clé. Si le message avait en fait été mis en file avant l'erreur, le réessai répond `409 already_sent` avec son `id`, et rien n'est débité deux fois. Voir [Idempotence](https://dzbuild.com/fr/fr/api-docs/idempotency.md).

## Limites connues[​](#limites-connues "Lien direct vers Limites connues")

* **Texte du délai de livraison.** `shipped_home` porte le délai de livraison tel que le marchand l'a saisi dans les réglages de l'addon. Si ce champ est vide, il porte le délai par défaut de l'addon, écrit dans la langue des messages choisie dans ses réglages (ou la langue de la boutique si aucune n'a été choisie). Un message en français d'une boutique dont le délai est écrit en arabe, saisi ou par défaut, affiche ce texte arabe au milieu du message français. Un délai écrit en chiffres, comme `24-72h`, se lit de la même façon dans les deux langues.
* **Envoi lent après une vérification de validation.** Quand le statut de validation enregistré d'un modèle a plus de 10 minutes, l'envoi le relit auprès de WhatsApp avant de mettre le message en file. Cela arrive au plus une fois par modèle et par langue toutes les 10 minutes et peut retenir le `POST` jusqu'à 15 secondes : donnez à votre client HTTP un délai d'attente d'au moins 20 secondes.
