Documentation développeur
Encaissez et versez par Mobile Money depuis vos serveurs
Une API REST en JSON, des écritures signées en HMAC-SHA256, une idempotence native et des notifications de statut signées. Lisez cette page en entier avant d'écrire votre première requête.
URL de base
https://tamoapi.tasa.pro/api/v1
Format
JSON UTF-8
Montants
Entiers, de 10 à 500 000
Limites
120 lectures et 30 écritures par minute
Démarrage
Trois façons de vous authentifier
Les lectures portent un jeton. Toute écriture (POST, PUT, PATCH, DELETE) est en plus signée avec votre api_secret.
Token API permanent
X-API-Token: tok_…
Recommandé pour l'intégration de serveur à serveur. Généré dans le dashboard, affiché une seule fois avec l'api_secret.
Token d'accès
Authorization: Bearer tat_…
Valable 60 minutes, de portée all, collection ou distribution. Obtenu avec Basic base64(merchant_code:api_secret). Les écritures restent signées.
Signature v2
X-Signature-Version: 2
Obligatoire sur chaque écriture, avec X-Timestamp, X-Nonce et Idempotency-Key. La clé est api_secret, jamais api_token.
- 01
Créez un compte
Inscription en ligne, puis validation par l'équipe TASA.
- 02
Générez vos clés
Dashboard, Clés API. Notez api_token et api_secret : ils ne s'affichent qu'une fois.
- 03
Déclarez votre IP
Obligatoire pour verser. Déclarez l'adresse publique sortante de vos serveurs.
- 04
Renseignez l'URL de rappel
Elle reçoit les changements de statut. Utilisez https://.
Signature des requêtes
Signez les octets exacts que vous envoyez
X-Signature = hex( HMAC-SHA256( METHOD + "\n" + URI + "\n" + X-Timestamp + "\n" + X-Nonce + "\n" + Idempotency-Key + "\n" + corps_brut, api_secret ) )
- URI : chemin complet et chaîne de requête, sans le domaine (/api/v1/collections).
- corps_brut : sérialisez une fois, signez, envoyez la même chaîne.
- Idempotency-Key : la valeur envoyée, ou une ligne vide si l'en-tête est absent.
- X-Timestamp : secondes Unix, tolérance de 300 s. X-Nonce : unique, 16 caractères au moins.
- Les lectures (GET) ne sont pas signées.
POST
/api/v1/collections
1767225600
9f86d081884c7d659a2feaa0c55ad015
collecte-CMD-10045
{"product_id":"f415c255-821d-4314-8628-61beea638215","operator_code":"MTN_CG","amount":5000,"phone_number":"+242060000000","merchant_reference":"CMD-10045"}Idempotence
Une Idempotency-Key par opération métier, dérivée de votre référence (collecte-CMD-10045), 190 caractères au plus. Même clé à chaque nouvelle tentative : la transaction existante est renvoyée en 200, rien n'est créé. Même clé pour une opération différente : 409. Délai dépassé ou 5xx : l'issue est inconnue, relancez avec la même clé.
Exemple
Client de référence et première collecte
Une fonction qui authentifie, signe et envoie. Identifiants lus dans TAMO_API_TOKEN et TAMO_API_SECRET.
// Node.js 18+ (fetch et crypto intégrés). Identifiants lus dans l'environnement.
const crypto = require('node:crypto');
const TAMO_BASE_URL = 'https://tamoapi.tasa.pro/api/v1';
async function tamoRequest(method, path, body = null, idempotencyKey = '') {
const url = new URL(TAMO_BASE_URL + path);
const raw = body === null ? '' : JSON.stringify(body); // sérialisé UNE fois
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-API-Token': process.env.TAMO_API_TOKEN,
};
// Toute écriture est signée, sur les octets EXACTS envoyés.
if (method !== 'GET') {
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(16).toString('hex');
const canonical = [method, url.pathname + url.search, timestamp, nonce, idempotencyKey, raw].join('\n');
headers['X-Signature-Version'] = '2';
headers['X-Timestamp'] = timestamp;
headers['X-Nonce'] = nonce;
headers['X-Signature'] = crypto
.createHmac('sha256', process.env.TAMO_API_SECRET)
.update(canonical, 'utf8')
.digest('hex');
if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;
}
// Une erreur réseau rend l'issue INCONNUE : relancez avec la MÊME clé.
const res = await fetch(url, { method, headers, body: method === 'GET' ? undefined : raw });
return { status: res.status, body: await res.json() };
}
// Encaisser (Cash In) : 201 créée, 200 rejeu de la même clé
const collecte = await tamoRequest('POST', '/collections', {
product_id: 'f415c255-821d-4314-8628-61beea638215',
operator_code: 'MTN_CG',
amount: 5000,
phone_number: '+242060000000',
merchant_reference: 'CMD-10045',
payer_name: 'Client Exemple',
description: 'Commande CMD-10045',
}, 'collecte-CMD-10045');
console.log(collecte.status, collecte.body.data?.reference ?? collecte.body.message);
// Suivre une transaction
const statut = await tamoRequest('GET', '/collections/status/' + encodeURIComponent(collecte.body.data.reference));
console.log(statut.body.data.status);{
"message": "Transaction initiée avec succès.",
"data": {
"id": "dca9d0a4-fa57-4fcd-b772-3c6ce403369d",
"reference": "TAMOC-OP7Y-261002-M8XFKU",
"merchant_reference": "CMD-10045",
"type": "collection",
"status": "pending",
"under_review": false,
"operator": { "code": "MTN_CG", "name": "Opérateur" },
"currency": "XAF",
"amounts": { "requested": 5000, "fee": 100, "net": 4900 },
"solde": { "sens": "credit", "montant": 4900, "applique": false },
"expires_at": "2026-10-02T10:58:49.000000Z"
}
}Référence
Endpoints
Toutes les routes sont relatives à https://tamoapi.tasa.pro/api/v1.
Authentification
- POST
/token
Token d'accès de portée all (60 min) Basic
- POST
/collection/token
Token limité aux encaissements Basic
- POST
/distribution/token
Token limité aux versements Basic
- POST
/credentials/token
Renouveler api_token (api_secret inchangé) Bearer
- POST
/credentials/secret
Renouveler api_secret Bearer
Collection (Cash In)
- POST
/collections
Initier une collecte Token + signature
- GET
/collections/status/{reference}
Statut par référence TAMO Token
- GET
/collections/{id}
Détail par identifiant Token
Disbursement (Cash Out)
- POST
/distributions
Initier un versement Token + signature
- GET
/distributions/status/{reference}
Statut par référence TAMO Token
- GET
/distributions/{id}
Détail par identifiant Token
Compte
- GET
/balances
Soldes par opérateur, devise et produit Token
- GET
/transactions
Historique paginé, base de la réconciliation Token
- GET
/products
Produits actifs : valeurs de product_id Token
- GET
/operators
Opérateurs utilisables : valeurs de operator_code Token
- POST
/sms
Envoyer un SMS (service SMS activé) Token + signature
Cash In : POST /collections
Le client paie depuis son Mobile Money. Statut pending jusqu'à sa validation ou 30 minutes.
- product_id*
- Produit actif de votre compte (GET /products).
- operator_code*
- Opérateur ouvert à la collecte pour ce produit.
- amount*
- Entier, unité de la devise. De 10 à 500 000.
- phone_number*
- Numéro du payeur, 8 à 15 chiffres, indicatif compris.
- merchant_reference
- Votre référence (100 car. max), renvoyée dans les notifications.
- payer_name
- Nom du payeur (150 car. max).
- payer_email
- Email du payeur (150 car. max).
- description
- Libellé (500 car. max).
- metadata
- Données libres, renvoyées telles quelles.
* obligatoire
Cash Out : POST /distributions
Vous versez sur le Mobile Money du bénéficiaire. Exige une IP déclarée et un solde suffisant.
- product_id*
- Produit actif ouvert à la distribution.
- operator_code*
- Opérateur du bénéficiaire.
- amount*
- De 10 à 500 000. Montant reçu ; les frais s'y ajoutent sur votre solde.
- phone_number*
- Numéro du bénéficiaire, 8 à 15 chiffres, indicatif compris.
- merchant_reference
- Votre référence de versement (100 car. max).
- recipient_name
- Nom du bénéficiaire (150 car. max).
- description
- Motif (500 car. max).
- metadata
- Données libres.
* obligatoire
Cycle de vie
Statuts d'une transaction
Attendre la notification (validation du client, traitement opérateur).
Attendre.
Livrer ou clôturer.
Proposer une nouvelle opération (nouvelle clé). En distribution, le montant est restitué.
Collecte non validée en 30 minutes.
Mettre à jour votre comptabilité.
Mettre à jour votre comptabilité.
under_review: true: l'opérateur n'a pas donné de réponse définitive. La transaction reste pending, les fonds restent bloqués, nos équipes vérifient. Ne relancez pas : le statut définitif arrivera par notification.
Notifications
Webhooks signés
À chaque changement de statut, la plateforme envoie un POST à l'URL de rappel de votre produit ou à vos webhooks déclarés. Vérifiez X-Webhook-Signature sur le corps brut avant tout traitement.
Événements
collection.pendingcollection.successcollection.failedcollection.expireddistribution.pendingdistribution.successdistribution.failedwebhook.test
- En-têtes : X-Webhook-Event, X-Webhook-Signature, X-Webhook-Attempt, et X-Webhook-Test pour webhook.test.
- Secret : propre à chaque destination, affiché à la création d'un webhook ou par le test de l'URL de rappel d'un produit.
- Réponse : 2xx en moins de 30 secondes. Sur 5xx, 408, 425 ou 429, jusqu'à 4 tentatives (après 1, 5 et 15 minutes). Les autres 4xx abandonnent.
- Doublons : une notification peut arriver plusieurs fois ou dans le désordre. Dédupliquez sur data.reference + event et ne revenez jamais d'un statut définitif à un statut en cours.
{
"event": "collection.success",
"timestamp": "2026-10-02T10:29:12.000000Z",
"data": {
"reference": "TAMOC-OP7Y-261002-M8XFKU",
"merchant_reference": "CMD-10045",
"type": "collection",
"status": "success",
"amount": 5000,
"fee": 100,
"net_amount": 4900,
"currency": "XAF",
"phone_number": "+242060000000",
"operator": "MTN_CG",
"completed_at": "2026-10-02T10:29:12.000000Z"
}
}// Node.js + Express. express.raw : la signature porte sur les octets BRUTS.
const crypto = require('node:crypto');
const express = require('express');
const app = express();
app.post('/webhooks/tamo', express.raw({ type: 'application/json' }), (req, res) => {
const expected = crypto
.createHmac('sha256', process.env.TAMO_WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
const received = String(req.get('X-Webhook-Signature') || '');
// Comparaison à temps constant, AVANT tout traitement.
const valid = received.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!valid) return res.sendStatus(401);
const event = JSON.parse(req.body.toString('utf8'));
// 1. Dédupliquer sur event.data.reference + event.event
// 2. Mettre en file, puis répondre 2xx rapidement
// 3. Ne jamais revenir d'un statut définitif à un statut en cours
res.sendStatus(200);
});
app.listen(3000);Erreurs
Codes de réponse
Toute erreur renvoie un JSON avec message, souvent complété par errors, action ou causes. Chaque réponse porte un en-tête X-Request-Id à communiquer au support.
| HTTP | Cause | Réaction de votre code |
|---|---|---|
| 200 | Rejeu d'une Idempotency-Key (en-tête Idempotent-Replay: true). | Traiter comme la transaction d'origine. |
| 400 | Idempotency-Key absente ou trop longue ; X-Signature-Version inconnue. | Corriger la requête. |
| 401 | Authentification absente ou révoquée ; signature absente, invalide ou v1 sur une route d'argent ; X-Timestamp hors fenêtre ; X-Nonce déjà utilisé. | Vérifier les identifiants, synchroniser l'horloge, un nonce neuf à chaque envoi. Ne pas réessayer à l'identique. |
| 403 | IP non déclarée ou liste blanche vide ; token hors de sa portée ; compte suspendu. | Déclarer l'IP dans le portail, utiliser un token de bonne portée. |
| 404 | Produit inconnu ou non actif ; transaction inconnue. | Relire GET /products. |
| 409 | Idempotency-Key réutilisée avec un autre montant, numéro, produit ou opérateur. | Une clé par opération. |
| 422 | Champ invalide, montant hors bornes, opérateur non ouvert à ce service, ou solde de distribution insuffisant. | Corriger ; approvisionner le solde. Ne pas réessayer à l'identique. |
| 429 | Limite de débit atteinte. | Attendre Retry-After secondes. |
| 5xx | Incident, ou réponse perdue : l'issue est inconnue. | Relancer avec la même Idempotency-Key, ou consulter le statut. |
Mise en production
Avant de passer en production
- Identifiants en coffre ; horloge synchronisée (NTP).
- Écritures signées en v2 ; corps envoyé identique au corps signé.
- Idempotency-Key dérivée de la référence et réutilisée à chaque nouvelle tentative.
- IP de production déclarées ; URL de rappel en HTTPS, signature vérifiée, réponse 2xx rapide, traitement dédupliqué.
- under_review géré sans relance ; réconciliation quotidienne avec GET /transactions.
- Premier encaissement et premier versement de faible montant validés de bout en bout.