Prérequis
Avant de commencer, assurez-vous d’avoir :- Vos credentials Remita :
username,password,apiKey,apiId - Un serveur HTTPS capable de recevoir les callbacks webhook
- Un UUID v4 généré côté serveur pour chaque transaction (
externalId)
Étape 1 — Obtenir un token d’accès
AppelezPOST /public/access_token avec vos identifiants. Stockez le access_token et le refresh_token retournés.
curl -X POST https://api.remita.cm/public/access_token \
-H "Content-Type: application/json" \
-d '{
"username": "votre@email.com",
"password": "votre_mot_de_passe"
}'
async function getAccessToken(username, password) {
const response = await fetch("https://api.remita.cm/public/access_token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password })
});
return await response.json();
}
import requests
def get_access_token(username: str, password: str) -> dict:
response = requests.post(
"https://api.remita.cm/public/access_token",
json={"username": username, "password": password}
)
response.raise_for_status()
return response.json()
token_data = get_access_token("votre@email.com", "votre_mot_de_passe")
access_token = token_data["access_token"]
refresh_token = token_data["refresh_token"]
<?php
function getAccessToken(string $username, string $password): array {
$ch = curl_init('https://api.remita.cm/public/access_token');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(compact('username', 'password')),
]);
$body = curl_exec($ch);
curl_close($ch);
return json_decode($body, true);
}
$tokenData = getAccessToken('votre@email.com', 'votre_mot_de_passe');
$accessToken = $tokenData['access_token'];
public void authenticate(String username, String password) throws Exception {
String body = String.format(
"{\"username\":\"%s\",\"password\":\"%s\"}", username, password
);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.remita.cm/public/access_token"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
Réponse
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
Le token expire après
expires_in secondes. Appelez POST /public/refresh_token avec le refreshToken pour le renouveler sans redemander les identifiants.Étape 2 — Initier une collecte
Une collecte débite le compte mobile money d’un client et crédite votre compte Remita.| Client | transferMethod |
|---|---|
| Orange Money CM | OMCM |
| MTN Mobile Money | MOMOCM |
curl -X POST https://api.remita.cm/api/v1/transaction/collect \
-H "Content-Type: application/json" \
-H "apiKey: YOUR_API_KEY" \
-H "apiId: YOUR_API_ID" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"transferMethod": "OMCM",
"customerName": "Jean Dupont",
"externalId": "550e8400-e29b-41d4-a716-446655440000",
"phoneNumber": "237690000000",
"amount": 5000,
"webhookUrl": "https://votre-serveur.com/remita/callback",
"countryName": "CAMEROON"
}'
async function initiateCollect(accessToken, apiKey, apiId) {
const payload = {
transferMethod: "OMCM",
customerName: "Jean Dupont",
externalId: crypto.randomUUID(),
phoneNumber: "237690000000",
amount: 5000,
webhookUrl: "https://votre-serveur.com/remita/callback",
countryName: "CAMEROON"
};
const response = await fetch("https://api.remita.cm/api/v1/transaction/collect", {
method: "POST",
headers: {
"Content-Type": "application/json",
"apiKey": apiKey,
"apiId": apiId,
"Authorization": `Bearer ${accessToken}`
},
body: JSON.stringify(payload)
});
return await response.json();
}
import requests, uuid
def initiate_collect(access_token: str, api_key: str, api_id: str) -> dict:
headers = {
"Content-Type": "application/json",
"apiKey": api_key,
"apiId": api_id,
"Authorization": f"Bearer {access_token}",
}
payload = {
"transferMethod": "OMCM",
"customerName": "Jean Dupont",
"externalId": str(uuid.uuid4()),
"phoneNumber": "237690000000",
"amount": 5000,
"webhookUrl": "https://votre-serveur.com/remita/callback",
"countryName": "CAMEROON",
}
response = requests.post(
"https://api.remita.cm/api/v1/transaction/collect",
headers=headers, json=payload
)
response.raise_for_status()
return response.json()
<?php
function initiateCollect(string $accessToken, string $apiKey, string $apiId): array {
$payload = [
'transferMethod' => 'OMCM',
'customerName' => 'Jean Dupont',
'externalId' => sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
),
'phoneNumber' => '237690000000',
'amount' => 5000,
'webhookUrl' => 'https://votre-serveur.com/remita/callback',
'countryName' => 'CAMEROON',
];
$ch = curl_init('https://api.remita.cm/api/v1/transaction/collect');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"apiKey: $apiKey", "apiId: $apiId",
"Authorization: Bearer $accessToken",
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$body = curl_exec($ch);
curl_close($ch);
return json_decode($body, true);
}
Réponse
{
"transactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"externalId": "550e8400-e29b-41d4-a716-446655440000",
"transferMethod": "OMCM",
"customerName": "Jean Dupont",
"customerPhone": "237690000000",
"payToken": "abc123xyz",
"amount": 5000,
"feesSystem": 50,
"feesApp": 25,
"transactionStatus": "PENDING"
}
Conservez le
transactionId — il est nécessaire pour vérifier le statut et traiter le webhook.Étape 3 — Recevoir le résultat via webhook
Dès que la transaction atteint un état final (SUCCESS ou FAILED), Remita envoie un POST vers votre webhookUrl.
@RestController
@RequestMapping("/remita")
public class RemitaWebhookController {
@PostMapping("/callback")
public ResponseEntity<String> handleCallback(@RequestBody Map<String, Object> payload) {
String transactionId = String.valueOf(payload.get("transactionId"));
String externalId = String.valueOf(payload.get("externalId"));
String status = String.valueOf(payload.get("status"));
if ("SUCCESS".equals(status)) {
System.out.println("Paiement reçu pour " + externalId);
// Créditer le compte client, envoyer confirmation...
} else if ("FAILED".equals(status)) {
System.err.println("Paiement échoué pour " + externalId);
}
return ResponseEntity.ok("OK");
}
}
from fastapi import FastAPI, Request
from fastapi.responses import PlainTextResponse
app = FastAPI()
@app.post("/remita/callback")
async def remita_callback(request: Request):
payload = await request.json()
external_id = payload.get("externalId")
status = payload.get("status")
if status == "SUCCESS":
print(f"Paiement reçu — externalId: {external_id}")
elif status == "FAILED":
print(f"Paiement échoué — externalId: {external_id}")
# IMPORTANT : toujours répondre 200 dans les 5 secondes
return PlainTextResponse("OK", status_code=200)
<?php
$payload = json_decode(file_get_contents('php://input'), true);
if (!$payload) { http_response_code(400); exit; }
$externalId = $payload['externalId'] ?? null;
$status = $payload['status'] ?? null;
if ($status === 'SUCCESS') {
error_log("Paiement reçu — externalId: $externalId");
} elseif ($status === 'FAILED') {
error_log("Paiement échoué — externalId: $externalId");
}
http_response_code(200);
echo 'OK';
app.post('/remita/callback', (req, res) => {
const { transactionId, externalId, status, amount } = req.body;
if (status === 'SUCCESS') {
console.log(`Paiement reçu — externalId: ${externalId}, montant: ${amount}`);
} else if (status === 'FAILED') {
console.error(`Paiement échoué — externalId: ${externalId}`);
}
// Toujours répondre 200 dans les 5 secondes
res.status(200).send('OK');
});
Votre endpoint webhook doit retourner
HTTP 200 dans les 5 secondes. Déléguez tout traitement lourd à une file de tâches et répondez immédiatement.Étape 4 — Vérifier le statut manuellement (polling)
Si vous n’avez pas reçu le webhook, vérifiez le statut avec letransactionId :
import requests, time
def poll_transaction_status(
transaction_id: str, access_token: str, api_key: str, api_id: str,
max_attempts: int = 10, interval_seconds: int = 5
) -> str:
headers = {
"apiKey": api_key, "apiId": api_id,
"Authorization": f"Bearer {access_token}",
}
for attempt in range(max_attempts):
data = requests.post(
"https://api.remita.cm/api/v1/transaction/transaction-status",
headers=headers, params={"id": transaction_id}
).json()
status = data.get("status")
print(f"Tentative {attempt + 1} — Statut: {status}")
if status in ("SUCCESS", "FAILED"):
return status
time.sleep(interval_seconds)
return "TIMEOUT"
async function pollTransactionStatus(transactionId, accessToken, apiKey, apiId) {
const headers = {
"apiKey": apiKey, "apiId": apiId,
"Authorization": `Bearer ${accessToken}`
};
for (let i = 0; i < 10; i++) {
const res = await fetch(
`https://api.remita.cm/api/v1/transaction/transaction-status?id=${transactionId}`,
{ method: "POST", headers }
);
const data = await res.json();
if (["SUCCESS", "FAILED"].includes(data.status)) return data.status;
await new Promise(r => setTimeout(r, 5000));
}
return "TIMEOUT";
}
Étape 5 — Initier un dépôt (optionnel)
Un dépôt envoie de l’argent depuis votre compte Remita vers le compte mobile money d’un bénéficiaire. La structure est identique à la collecte :POST /api/v1/transaction/deposit
# Même corps que /collect — seul le sens du flux change
Référence rapide
| Action | Méthode | Endpoint |
|---|---|---|
| Obtenir un token | POST | /public/access_token |
| Renouveler le token | POST | /public/refresh_token |
| Initier une collecte | POST | /api/v1/transaction/collect |
| Initier un dépôt | POST | /api/v1/transaction/deposit |
| Vérifier le statut | POST | /api/v1/transaction/transaction-status?id=<id> |
| Valider une transaction | POST | /api/v1/transaction/validateTransaction |
| Rejeter une transaction | POST | /api/v1/transaction/rejectTransaction |
| Lister les transactions | GET | /api/v1/transaction/getByApplicationProduct |
| Consulter les soldes | GET | /api/v1/transaction/getByApplicationProductBalances |
Checklist avant mise en production
Tokens stockés de manière sécurisée (jamais en clair dans le code ou les logs)
apiKey jamais exposée côté client (frontend / mobile)externalId unique UUID v4 par transactiontransferMethod cohérent avec countryNameEndpoint webhook retournant
HTTP 200 en moins de 5 secondesGestion du renouvellement automatique du token (refresh)
Mécanisme de polling en fallback si webhook non reçu
Logs de toutes les transactions (
externalId, transactionId, statut)