API v1
Documentation
Tout ce qu'il faut pour envoyer votre premier SMS depuis votre code : une clé, un appel, un webhook.
Démarrer
L'API MMSDuck est une API REST. Toutes les requêtes se font en HTTPS, les corps de requête et de réponse sont en JSON encodé en UTF-8.
https://api.mmsduck.comAvant votre premier envoi, il vous faut trois choses :
- un compte marchand ;
- une clé API active ;
- un solde de crédits : un SMS d'un segment coûte 50 Ar.
Authentification
Chaque requête s'authentifie avec une clé API transmise dans l'en-tête Authorization, selon le schéma Bearer.
Authorization: Bearer mmsduck_live_…Une clé révoquée est refusée immédiatement, avec une réponse 401. Vous pouvez faire coexister plusieurs clés, par exemple une par application, pour en révoquer une sans interrompre les autres.
Bac à sable et production
Deux environnements, deux préfixes de clé : mmsduck_test_… pour le bac à sable, mmsduck_live_… pour la production. L'environnement vient toujours de la clé présentée, jamais d'un paramètre de la requête.
Le bac à sable ne demande aucune vérification d'identité, mais ne peut écrire qu'aux numéros que vous avez vous-même déclarés et validés (menu « Numéros de test » du tableau de bord, trois numéros au maximum). La production exige un dossier d'identité approuvé. Dans les deux cas, l'envoi débite réellement vos crédits : le bac à sable n'est pas gratuit.
Envoyer un SMS
Crée un message et le place en file d'envoi. La réponse est immédiate : elle confirme la prise en charge, pas encore la livraison.
| Paramètre | Type | Description |
|---|---|---|
| to | string | Numéro du destinataire. Les formats locaux sont acceptés puis normalisés en +261XXXXXXXXX. Requis. |
| message | string | Contenu du SMS, sans le nom de votre entreprise : il est ajouté automatiquement en première ligne (voir plus bas). Au-delà d'un segment, en-tête comprise, le message est découpé et chaque segment consomme un crédit. Requis. |
curl -X POST https://api.mmsduck.com/api/v1/sms/send \
-H "Authorization: Bearer mmsduck_live_…" \
-H "Content-Type: application/json" \
-d '{
"to": "+261341234567",
"message": "Votre code est 123456"
}'{
"success": true,
"message_id": "sms_01J8ABC123",
"status": "queued"
}Conservez le message_id : c'est lui qui vous permet de retrouver le message et de recouper les webhooks reçus.
Nom de l'expéditeur
Le nom de votre entreprise est ajouté automatiquement en première ligne de chaque message, suivi d'un retour à la ligne. Vous n'avez rien à faire : envoyez seulement votre texte.
BOUTIQUE VANILLE
Votre code est 123456Suivre un message
Renvoie l'état courant d'un message. En pratique, les webhooks vous préviennent sans que vous ayez à interroger cette route : réservez-la aux vérifications ponctuelles et aux réconciliations.
| Statut | Signification |
|---|---|
| queued | Le message est accepté et attend son tour. |
| processing | Le message est en cours d'envoi. |
| sent | Le message est parti vers le réseau mobile. |
| failed | L'envoi a échoué définitivement, après épuisement des tentatives. |
| cancelled | Le message a été annulé avant son envoi. |
{
"message_id": "sms_01J8ABC123",
"to": "+261341234567",
"message": "Votre code est 123456",
"status": "sent",
"segments": 1,
"attempts": 1,
"error": null,
"created_at": "2026-08-10T09:41:00Z",
"processed_at": "2026-08-10T09:41:02Z",
"sent_at": "2026-08-10T09:41:03Z"
}Webhooks
Déclarez une URL de rappel dans votre tableau de bord. Quand un message atteint un état terminal, envoyé ou définitivement échoué, MMSDuck y envoie une requête POST décrivant le message concerné. Les états intermédiaires (en attente, en cours) ne déclenchent pas de webhook : suivez-les en direct depuis votre tableau de bord si vous en avez besoin.
X-MMSDuck-Signature: sha256=5f2b9c1e8a3d…
{
"message_id": "sms_01J8ABC123",
"status": "sent",
"to": "+261341234567"
}Vérifier la signature
Chaque appel porte un en-tête X-MMSDuck-Signature, au format sha256=<hex> : un HMAC-SHA256 du corps brut de la requête, calculé avec le secret de votre webhook (whsec_…, visible dans Webhooks sur votre tableau de bord). Recalculez-le de votre côté et comparez : si les valeurs diffèrent, ignorez la notification, elle ne vient pas de nous.
mmsduck_test_… ne livre jamais sur votre webhook de production, et inversement. Configurez les deux séparément dans Webhooks, en sélectionnant l'espace concerné.import crypto from "node:crypto";
// rawBody = corps BRUT de la requête (pas l'objet JSON déjà parsé)
const header = req.headers["x-mmsduck-signature"]; // "sha256=<hex>"
const expected = "sha256=" + crypto
.createHmac("sha256", process.env.MMSDUCK_WEBHOOK_SECRET) // whsec_…
.update(rawBody)
.digest("hex");
const valid =
header.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
if (!valid) {
return res.status(400).send("Signature invalide");
}Réessais
Répondez 2xx dès réception, avant même votre traitement métier. Toute autre réponse, ou une absence de réponse, est considérée comme un échec, et la notification est représentée plus tard.
Erreurs
Les erreurs utilisent les codes HTTP usuels et renvoient toujours un corps JSON de même forme.
{
"success": false,
"error": {
"code": "insufficient_credits",
"message": "Solde insuffisant pour envoyer ce message."
}
}| HTTP | Code | Cause |
|---|---|---|
| 400 | invalid_request | Paramètre manquant ou corps de requête illisible. |
| 400 | invalid_recipient | Le numéro du destinataire n'est pas un numéro mobile malgache valide. |
| 401 | invalid_api_key | Clé absente, inconnue ou révoquée. |
| 402 | insufficient_credits | Le solde ne couvre pas le nombre de segments du message. |
| 403 | kyc_not_approved | Clé de production, mais votre dossier d'identité n'est pas encore approuvé. Utilisez une clé de bac à sable en attendant. |
| 403 | recipient_not_verified | Clé de bac à sable envoyée vers un numéro que vous n'avez pas déclaré et validé. |
| 403 | bulk_content_refused | Ce message, identique, a déjà été envoyé à trop de destinataires distincts en peu de temps : signature d'un envoi en masse, interdit par les conditions générales. |
| 404 | sms_not_found | Aucun message avec cet identifiant sur votre compte. |
| 429 | rate_limited | Limite d'envoi atteinte. Réessayez plus tard. |
| 429 | daily_limit_reached | Plafond de SMS sur 24 h atteint. Contactez-nous si votre volume légitime le dépasse. |
| 503 | insufficient_platform_capacity | La plateforme n'a temporairement plus de capacité d'envoi. Réessayez plus tard, votre solde n'a pas été débité. |
| 500 | internal_error | Incident de notre côté. Le message n'a pas été débité. |
Limites d'envoi
Deux compteurs indépendants s'appliquent à chaque envoi, par clé API et par compte marchand : par défaut 2 requêtes par seconde et 60 par minute. Un pic ponctuel sur l'un des deux suffit à déclencher la limite, même si l'autre est encore large.
Au-delà, l'API répond 429 (rate_limited) sans débiter de crédit. Espacez alors vos appels, en allongeant le délai à chaque nouvel échec plutôt qu'en réessayant immédiatement. Un volume plus élevé que ces limites par défaut ? Contactez-nous, elles sont ajustables par compte.
Prêt à envoyer ?
Créez votre compte, générez une clé et faites partir un message de test.
Créer un compte