Unifier la recharge et le suivi — en un seul flux.
Première API de recharge automatique au Maroc, MoroccoTopup connecte vos services aux opérateurs télécoms pour proposer des recharges instantanées et suivies, en quelques appels.
- Tout type de recharge: mobile, data et offres promo.
- Pour revendeurs, boutiques, fintech, IoT et plateformes de services.
- Idéal pour vendre la recharge comme produit, cadeau ou avantage client.
- Suivi temps réel + webhooks sécurisés pour automatiser vos flux.
Unify recharge and tracking — in a single flow.
Morocco's first automated topup API, MoroccoTopup connects your apps to telecom operators so you can sell instant, trackable recharges with just a few calls.
- All topup types: mobile, data, and promo offers.
- Built for resellers, shops, fintech, IoT, and service platforms.
- Perfect for topups as a product, gift, or customer reward.
- Real‑time tracking + secure webhooks to automate your workflows.
Unifica la recarga y el seguimiento — en un solo flujo.
La primera API de recargas automáticas en Marruecos, MoroccoTopup conecta tus sistemas con los operadores para vender recargas instantáneas y trazables.
- Todo tipo de recargas: móvil, datos y ofertas promo.
- Para revendedores, tiendas, fintech, IoT y plataformas de servicios.
- Ideal para vender recargas como producto, regalo o beneficio.
- Seguimiento en tiempo real + webhooks seguros para automatizar.
Démarrage rapide Quick start Inicio rápido
Base URL Base URL Base URL
https://api.moroccotopup.com/rest/V1/recharge-apihttps://api.moroccotopup.com/rest/V1/recharge-api/sandbox- Base URL
- X-Client-Code
- X-Api-Key
Pour vérifier les webhooks, utilisez votre secret API (HMAC). To verify webhooks, use your API secret (HMAC). Para verificar webhooks, use su secreto API (HMAC).
Chaque requête doit contenir:
- X-Client-Code: code unique du client API
- X-Api-Key: clé API (affichée une seule fois)
Les opérateurs acceptés: Orange, Inwi, Maroc Telecom.
Every request must include:
- X-Client-Code: API client code
- X-Api-Key: API key (shown once)
Allowed operators: Orange, Inwi, Maroc Telecom.
Cada solicitud debe incluir:
- X-Client-Code: código del cliente API
- X-Api-Key: clave API (se muestra una sola vez)
Operadores permitidos: Orange, Inwi, Maroc Telecom.
Les réponses sont en JSON (UTF‑8). Les erreurs utilisent le code HTTP approprié.
- operator est sensible à la casse: Orange, Inwi, Maroc Telecom.
- phone est le numéro MSISDN (ex: 0612345678).
- type correspond au promo de recharge (ex: 6 => promo *6).
- value est la valeur faciale de la recharge (MAD).
- billing_amount est le montant facturé.
- status_code est un code numérique standardisé (voir section Statuts).
- ref_interne est la référence MoroccoTopup.
- external_reference est la référence client.
- external_reference est unique par client (retourne 409 en cas de doublon).
Format d’erreur (exemple):
{
"message": "Référence externe déjà utilisée.",
"parameters": { "code": "DUPLICATE_REFERENCE" }
}
Responses are JSON (UTF‑8). Errors use appropriate HTTP status codes.
- operator is case-sensitive: Orange, Inwi, Maroc Telecom.
- phone is the MSISDN number (ex: 0612345678).
- type maps to the recharge promo (ex: 6 => promo *6).
- value is the face value (MAD).
- billing_amount is the billed amount.
- status_code is a standardized numeric status (see Status Codes).
- ref_interne is the MoroccoTopup reference.
- external_reference is the client reference.
- external_reference is unique per client (returns 409 on duplicates).
Error format (example):
{
"message": "External reference already used.",
"parameters": { "code": "DUPLICATE_REFERENCE" }
}
Las respuestas son JSON (UTF‑8). Los errores usan el código HTTP adecuado.
- operator distingue mayúsculas: Orange, Inwi, Maroc Telecom.
- phone es el número MSISDN (ej: 0612345678).
- type corresponde al promo de la recarga (ej: 6 => promo *6).
- value es el valor facial (MAD).
- billing_amount es el monto facturado.
- status_code es un estado numérico estándar (ver Estados).
- ref_interne es la referencia de MoroccoTopup.
- external_reference es la referencia del cliente.
- external_reference es único por cliente (devuelve 409 en duplicados).
Formato de error (ejemplo):
{
"message": "Referencia externa ya utilizada.",
"parameters": { "code": "DUPLICATE_REFERENCE" }
}
La sandbox reflète la production : mêmes endpoints, mêmes formats, même authentification. Utilisez simplement une base URL différente.
https://api.moroccotopup.com/rest/V1/recharge-api/sandbox
- /sandbox/recharge simule une recharge et génère un scénario réaliste.
- Les recharges simulées sont stockées et visibles via /sandbox/history, /sandbox/recharge/by-ref et /sandbox/recharge/by-id.
- /sandbox/health permet de vérifier la disponibilité de la sandbox.
- Aucun débit réel n’est effectué.
- ref_interne commence par SBX-.
- Rétention : 30 jours.
- Les webhooks sont réels si un webhook_url est défini.
Sandbox mirrors production: same endpoints, same payloads, same auth. Just use a different base URL.
https://api.moroccotopup.com/rest/V1/recharge-api/sandbox
- /sandbox/recharge simulates a recharge with realistic scenarios.
- Simulated recharges are stored and available via /sandbox/history, /sandbox/recharge/by-ref and /sandbox/recharge/by-id.
- /sandbox/health checks sandbox availability.
- No real debit is performed.
- ref_interne starts with SBX-.
- Retention: 30 days.
- Webhooks are real if a webhook_url is configured.
La sandbox refleja producción: mismos endpoints, mismos formatos, misma autenticación. Solo usa una base URL diferente.
https://api.moroccotopup.com/rest/V1/recharge-api/sandbox
- /sandbox/recharge simula una recarga con escenarios realistas.
- Las recargas simuladas se guardan y están disponibles en /sandbox/history, /sandbox/recharge/by-ref y /sandbox/recharge/by-id.
- /sandbox/health verifica la disponibilidad de la sandbox.
- No se realiza ningún débito real.
- ref_interne comienza con SBX-.
- Retención: 30 días.
- Los webhooks son reales si hay webhook_url configurado.
/me
Informations du compte API. API account information. Información de la cuenta API.
/balance
Solde + devise + limite de crédit. Balance + currency + credit limit. Saldo + divisa + límite de crédito.
/prices
Grille tarifaire par opérateur. Price list per operator. Lista de precios por operador.
/recharge
Recharge unitaire. Single recharge. Recarga unitaria.
/recharge/bulk
Recharge multiple. Bulk recharge. Recarga múltiple.
/recharge/by-ref/:externalReference
Recherche par référence externe. Lookup by external reference. Búsqueda por referencia externa.
/recharge/by-id/:id
Recherche par référence interne. Lookup by internal reference. Búsqueda por referencia interna.
/history
Historique paginé. Paginated history. Historial paginado.
/webhook
Lire la config webhook. Read webhook config. Leer configuración de webhook.
/webhook
Mettre à jour la config webhook. Update webhook config. Actualizar configuración de webhook.
/health
Vérification de disponibilité. Availability check. Comprobación de disponibilidad.
La sandbox reprend les mêmes endpoints avec le préfixe /sandbox + /sandbox/health.
Sandbox uses the same endpoints with the /sandbox prefix + /sandbox/health.
La sandbox usa los mismos endpoints con el prefijo /sandbox + /sandbox/health.
curl -X POST "https://api.moroccotopup.com/rest/V1/recharge-api/recharge" \
-H "Content-Type: application/json" \
-H "X-Client-Code: API-XXXX" \
-H "X-Api-Key: YOUR_KEY" \
-d '{
"phone": "0612345678",
"operator": "Inwi",
"value": 10,
"type": "6",
"external_reference": "TEST-001"
}'
$payload = [
'phone' => '0612345678',
'operator' => 'Inwi',
'value' => 10,
'type' => '6',
'external_reference' => 'TEST-001'
];
$ch = curl_init('https://api.moroccotopup.com/rest/V1/recharge-api/recharge');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Client-Code: API-XXXX',
'X-Api-Key: YOUR_KEY'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$response = curl_exec($ch);
const res = await fetch('https://api.moroccotopup.com/rest/V1/recharge-api/recharge', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Client-Code': 'API-XXXX',
'X-Api-Key': 'YOUR_KEY'
},
body: JSON.stringify({
phone: '0612345678',
operator: 'Inwi',
value: 10,
type: '6',
external_reference: 'TEST-001'
})
});
const data = await res.json();
HttpClient client = HttpClient.newHttpClient();
String payload = "{\"phone\":\"0612345678\",\"operator\":\"Inwi\",\"value\":10,\"type\":\"6\",\"external_reference\":\"TEST-001\"}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.moroccotopup.com/rest/V1/recharge-api/recharge"))
.header("Content-Type", "application/json")
.header("X-Client-Code", "API-XXXX")
.header("X-Api-Key", "YOUR_KEY")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
Réponse Response Respuesta
{
"success": true,
"status_code": 1,
"ref_interne": "189957125550360",
"external_reference": "TEST-001",
"value": 10,
"billing_amount": 9.35,
"balance_after": 70
}
GET /me
Retourne les informations du compte API authentifié. Returns the authenticated API account details. Devuelve los datos de la cuenta API autenticada.
curl -X GET "https://api.moroccotopup.com/rest/V1/recharge-api/me" \ -H "Content-Type: application/json" \ -H "X-Client-Code: API-XXXX" \ -H "X-Api-Key: YOUR_KEY"
$ch = curl_init('https://api.moroccotopup.com/rest/V1/recharge-api/me');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Client-Code: API-XXXX',
'X-Api-Key: YOUR_KEY'
]);
$response = curl_exec($ch);
const res = await fetch('https://api.moroccotopup.com/rest/V1/recharge-api/me', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Client-Code': 'API-XXXX',
'X-Api-Key': 'YOUR_KEY'
}
});
const data = await res.json();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.moroccotopup.com/rest/V1/recharge-api/me"))
.header("Content-Type", "application/json")
.header("X-Client-Code", "API-XXXX")
.header("X-Api-Key", "YOUR_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
{
"client_id": 7,
"name": "MAHI AHMED 3",
"code": "API-20251226-569",
"type": "reseller",
"status": "active",
"solde": 90,
"devise": "MAD",
"credit_limit": 0,
"ip_whitelist": "",
"webhook_url": "https://client.example/webhook",
"created_at": "2025-12-26 16:38:12",
"updated_at": "2026-01-09 19:22:12"
}
GET /balance
Solde, devise et limite de crédit. Balance, currency, and credit limit. Saldo, divisa y límite de crédito.
curl -X GET "https://api.moroccotopup.com/rest/V1/recharge-api/balance" \ -H "Content-Type: application/json" \ -H "X-Client-Code: API-XXXX" \ -H "X-Api-Key: YOUR_KEY"
$ch = curl_init('https://api.moroccotopup.com/rest/V1/recharge-api/balance');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Client-Code: API-XXXX',
'X-Api-Key: YOUR_KEY'
]);
$response = curl_exec($ch);
const res = await fetch('https://api.moroccotopup.com/rest/V1/recharge-api/balance', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Client-Code': 'API-XXXX',
'X-Api-Key': 'YOUR_KEY'
}
});
const data = await res.json();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.moroccotopup.com/rest/V1/recharge-api/balance"))
.header("Content-Type", "application/json")
.header("X-Client-Code", "API-XXXX")
.header("X-Api-Key", "YOUR_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
{
"balance": 90,
"devise": "MAD",
"credit_limit": 0
}
GET /prices
Grille tarifaire par opérateur pour ce client. Price list per operator for this client. Lista de precios por operador para este cliente.
curl -X GET "https://api.moroccotopup.com/rest/V1/recharge-api/prices" \ -H "Content-Type: application/json" \ -H "X-Client-Code: API-XXXX" \ -H "X-Api-Key: YOUR_KEY"
$ch = curl_init('https://api.moroccotopup.com/rest/V1/recharge-api/prices');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Client-Code: API-XXXX',
'X-Api-Key: YOUR_KEY'
]);
$response = curl_exec($ch);
const res = await fetch('https://api.moroccotopup.com/rest/V1/recharge-api/prices', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Client-Code': 'API-XXXX',
'X-Api-Key': 'YOUR_KEY'
}
});
const data = await res.json();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.moroccotopup.com/rest/V1/recharge-api/prices"))
.header("Content-Type", "application/json")
.header("X-Client-Code", "API-XXXX")
.header("X-Api-Key", "YOUR_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
{
"client_code": "API-20251226-569",
"devise": "MAD",
"prices": [
{ "operator": "orange", "margin_percent": 6.5 },
{ "operator": "inwi", "margin_percent": -3.0 }
]
}
POST /recharge
Recharge unitaire. Réponse avec status_code. Single recharge. Response includes status_code. Recarga unitaria. La respuesta incluye status_code.
curl -X POST "https://api.moroccotopup.com/rest/V1/recharge-api/recharge" \
-H "Content-Type: application/json" \
-H "X-Client-Code: API-XXXX" \
-H "X-Api-Key: YOUR_KEY" \
-d '{
"phone": "0612345678",
"operator": "Inwi",
"value": 10,
"type": "6",
"external_reference": "TEST-001"
}'
$payload = [
'phone' => '0612345678',
'operator' => 'Inwi',
'value' => 10,
'type' => '6',
'external_reference' => 'TEST-001'
];
$ch = curl_init('https://api.moroccotopup.com/rest/V1/recharge-api/recharge');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Client-Code: API-XXXX',
'X-Api-Key: YOUR_KEY'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$response = curl_exec($ch);
const res = await fetch('https://api.moroccotopup.com/rest/V1/recharge-api/recharge', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Client-Code': 'API-XXXX',
'X-Api-Key': 'YOUR_KEY'
},
body: JSON.stringify({
phone: '0612345678',
operator: 'Inwi',
value: 10,
type: '6',
external_reference: 'TEST-001'
})
});
const data = await res.json();
String payload = "{\"phone\":\"0612345678\",\"operator\":\"Inwi\",\"value\":10,\"type\":\"6\",\"external_reference\":\"TEST-001\"}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.moroccotopup.com/rest/V1/recharge-api/recharge"))
.header("Content-Type", "application/json")
.header("X-Client-Code", "API-XXXX")
.header("X-Api-Key", "YOUR_KEY")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
{
"success": true,
"status_code": 1,
"ref_interne": "189957125550360",
"external_reference": "TEST-001",
"value": 10,
"billing_amount": 9.35,
"balance_after": 70
}
POST /recharge/bulk
Recharge multiple. Chaque item retourne un succès ou une erreur. Bulk recharge. Each item returns success or error. Recarga múltiple. Cada item devuelve éxito o error.
curl -X POST "https://api.moroccotopup.com/rest/V1/recharge-api/recharge/bulk" \
-H "Content-Type: application/json" \
-H "X-Client-Code: API-XXXX" \
-H "X-Api-Key: YOUR_KEY" \
-d '{
"items": [
{ "phone": "0612345678", "operator": "Inwi", "value": 10, "type": "6", "external_reference": "BULK-001" },
{ "phone": "0612345679", "operator": "Orange", "value": 20, "type": "0", "external_reference": "BULK-002" }
]
}'
$payload = [
'items' => [
['phone' => '0612345678', 'operator' => 'Inwi', 'value' => 10, 'type' => '6', 'external_reference' => 'BULK-001'],
['phone' => '0612345679', 'operator' => 'Orange', 'value' => 20, 'type' => '0', 'external_reference' => 'BULK-002']
]
];
$ch = curl_init('https://api.moroccotopup.com/rest/V1/recharge-api/recharge/bulk');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Client-Code: API-XXXX',
'X-Api-Key: YOUR_KEY'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$response = curl_exec($ch);
const res = await fetch('https://api.moroccotopup.com/rest/V1/recharge-api/recharge/bulk', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Client-Code': 'API-XXXX',
'X-Api-Key': 'YOUR_KEY'
},
body: JSON.stringify({
items: [
{ phone: '0612345678', operator: 'Inwi', value: 10, type: '6', external_reference: 'BULK-001' },
{ phone: '0612345679', operator: 'Orange', value: 20, type: '0', external_reference: 'BULK-002' }
]
})
});
const data = await res.json();
String payload = "{\"items\":[{\"phone\":\"0612345678\",\"operator\":\"Inwi\",\"value\":10,\"type\":\"6\",\"external_reference\":\"BULK-001\"},{\"phone\":\"0612345679\",\"operator\":\"Orange\",\"value\":20,\"type\":\"0\",\"external_reference\":\"BULK-002\"}]}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.moroccotopup.com/rest/V1/recharge-api/recharge/bulk"))
.header("Content-Type", "application/json")
.header("X-Client-Code", "API-XXXX")
.header("X-Api-Key", "YOUR_KEY")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
{
"items": [
{ "success": true, "status_code": 1, "ref_interne": "189957125550360" },
{ "success": false, "error": "INSUFFICIENT_FUNDS" }
],
"balance": 70
}
GET /recharge/by-ref/:externalReference
Recherche par référence externe. Lookup by external reference. Búsqueda por referencia externa.
curl -X GET "https://api.moroccotopup.com/rest/V1/recharge-api/recharge/by-ref/TEST-001" \ -H "Content-Type: application/json" \ -H "X-Client-Code: API-XXXX" \ -H "X-Api-Key: YOUR_KEY"
$ch = curl_init('https://api.moroccotopup.com/rest/V1/recharge-api/recharge/by-ref/TEST-001');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Client-Code: API-XXXX',
'X-Api-Key: YOUR_KEY'
]);
$response = curl_exec($ch);
const res = await fetch('https://api.moroccotopup.com/rest/V1/recharge-api/recharge/by-ref/TEST-001', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Client-Code': 'API-XXXX',
'X-Api-Key': 'YOUR_KEY'
}
});
const data = await res.json();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.moroccotopup.com/rest/V1/recharge-api/recharge/by-ref/TEST-001"))
.header("Content-Type", "application/json")
.header("X-Client-Code", "API-XXXX")
.header("X-Api-Key", "YOUR_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
{
"ref_interne": "189957125550360",
"external_reference": "TEST-001",
"phone": "0612345678",
"operator": "Inwi",
"value": 10,
"billing_amount": 9.35,
"type": "6",
"status_code": 4,
"created_at": "2026-01-09 19:21:57",
"executed_at": "2026-01-09 19:22:00"
}
GET /recharge/by-id/:id
Recherche par référence interne (ref_interne). Lookup by internal reference (ref_interne). Búsqueda por referencia interna (ref_interne).
curl -X GET "https://api.moroccotopup.com/rest/V1/recharge-api/recharge/by-id/189957125550360" \ -H "Content-Type: application/json" \ -H "X-Client-Code: API-XXXX" \ -H "X-Api-Key: YOUR_KEY"
$ch = curl_init('https://api.moroccotopup.com/rest/V1/recharge-api/recharge/by-id/189957125550360');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Client-Code: API-XXXX',
'X-Api-Key: YOUR_KEY'
]);
$response = curl_exec($ch);
const res = await fetch('https://api.moroccotopup.com/rest/V1/recharge-api/recharge/by-id/189957125550360', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Client-Code': 'API-XXXX',
'X-Api-Key': 'YOUR_KEY'
}
});
const data = await res.json();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.moroccotopup.com/rest/V1/recharge-api/recharge/by-id/189957125550360"))
.header("Content-Type", "application/json")
.header("X-Client-Code", "API-XXXX")
.header("X-Api-Key", "YOUR_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
{
"id": "189957125550360",
"phone": "0612345678",
"operator": "Inwi",
"value": 10,
"billing_amount": 9.35,
"type": "6",
"status_code": 4,
"external_reference": "TEST-001",
"created_at": "2026-01-09 19:21:57",
"executed_at": "2026-01-09 19:22:00"
}
GET /history
Historique paginé. Paramètres: page, pageSize.
Paginated history. Params: page, pageSize.
Historial paginado. Parámetros: page, pageSize.
curl -X GET "https://api.moroccotopup.com/rest/V1/recharge-api/history?page=1&pageSize=20" \ -H "Content-Type: application/json" \ -H "X-Client-Code: API-XXXX" \ -H "X-Api-Key: YOUR_KEY"
$ch = curl_init('https://api.moroccotopup.com/rest/V1/recharge-api/history?page=1&pageSize=20');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Client-Code: API-XXXX',
'X-Api-Key: YOUR_KEY'
]);
$response = curl_exec($ch);
const res = await fetch('https://api.moroccotopup.com/rest/V1/recharge-api/history?page=1&pageSize=20', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Client-Code': 'API-XXXX',
'X-Api-Key': 'YOUR_KEY'
}
});
const data = await res.json();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.moroccotopup.com/rest/V1/recharge-api/history?page=1&pageSize=20"))
.header("Content-Type", "application/json")
.header("X-Client-Code", "API-XXXX")
.header("X-Api-Key", "YOUR_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
{
"items": [ { "id": "189957125550360", "phone": "0612345678", "status_code": 4 } ],
"pagination": { "page": 1, "page_size": 20, "total": 11 }
}
GET /webhook
Lire la configuration webhook. Read webhook configuration. Leer la configuración del webhook.
curl -X GET "https://api.moroccotopup.com/rest/V1/recharge-api/webhook" \ -H "Content-Type: application/json" \ -H "X-Client-Code: API-XXXX" \ -H "X-Api-Key: YOUR_KEY"
$ch = curl_init('https://api.moroccotopup.com/rest/V1/recharge-api/webhook');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Client-Code: API-XXXX',
'X-Api-Key: YOUR_KEY'
]);
$response = curl_exec($ch);
const res = await fetch('https://api.moroccotopup.com/rest/V1/recharge-api/webhook', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Client-Code': 'API-XXXX',
'X-Api-Key': 'YOUR_KEY'
}
});
const data = await res.json();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.moroccotopup.com/rest/V1/recharge-api/webhook"))
.header("Content-Type", "application/json")
.header("X-Client-Code", "API-XXXX")
.header("X-Api-Key", "YOUR_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
{
"webhook_url": "https://client.example/webhook",
"ip_whitelist": "1.2.3.4, 5.6.7.8"
}
POST /webhook
Mettre à jour l’URL webhook et la whitelist IP. Update webhook URL and IP whitelist. Actualizar la URL del webhook y la lista blanca de IP.
curl -X POST "https://api.moroccotopup.com/rest/V1/recharge-api/webhook" \
-H "Content-Type: application/json" \
-H "X-Client-Code: API-XXXX" \
-H "X-Api-Key: YOUR_KEY" \
-d '{
"webhook_url": "https://client.example/webhook",
"ip_whitelist": "1.2.3.4, 5.6.7.8"
}'
$payload = [
'webhook_url' => 'https://client.example/webhook',
'ip_whitelist' => '1.2.3.4, 5.6.7.8'
];
$ch = curl_init('https://api.moroccotopup.com/rest/V1/recharge-api/webhook');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Client-Code: API-XXXX',
'X-Api-Key: YOUR_KEY'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$response = curl_exec($ch);
const res = await fetch('https://api.moroccotopup.com/rest/V1/recharge-api/webhook', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Client-Code': 'API-XXXX',
'X-Api-Key': 'YOUR_KEY'
},
body: JSON.stringify({
webhook_url: 'https://client.example/webhook',
ip_whitelist: '1.2.3.4, 5.6.7.8'
})
});
const data = await res.json();
String payload = "{\"webhook_url\":\"https://client.example/webhook\",\"ip_whitelist\":\"1.2.3.4, 5.6.7.8\"}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.moroccotopup.com/rest/V1/recharge-api/webhook"))
.header("Content-Type", "application/json")
.header("X-Client-Code", "API-XXXX")
.header("X-Api-Key", "YOUR_KEY")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
{
"success": true,
"webhook_url": "https://client.example/webhook",
"ip_whitelist": "1.2.3.4, 5.6.7.8"
}
Les statuts sont transmis uniquement sous forme de code numérique. Statuses are sent only as numeric codes. Los estados se envían solo como códigos numéricos.
| Code Code Código | Signification Meaning Significado |
|---|---|
| 1 | File d'attente |
| 2 | En cours d'exécution |
| 3 | Effectué |
| 4 | Cette ligne est inexistante |
| 5 | Montant de la recharge est incorrect |
| 6 | Opération échouée |
| 7 | Le numéro introduit n'est pas disponible |
| 8 | Erreur interne |
| 9 | Problème interne |
| 10 | L'offre n'est pas valide |
| 11 | Remboursement du solde |
| 12 | Le numéro <numero> est suspendu |
| 16 | Tentative finale en attente de confirmation |
| 17 | En cours de vérification |
| 19 | Solde WhatsApp delivered |
| 20 | Convertie en solde WhatsApp |
| 21 | Préparation de la deuxième tentative |
| 22 | Deuxième tentative |
| 0 | Inconnu |
Nous envoyons un webhook à chaque changement de statut. Le webhook permet de synchroniser vos systèmes en temps réel.
Vous pouvez configurer l’URL:
- via l’interface Admin (Comptes API > Webhook URL)
- via l’API:
POST /webhook
Pour renforcer la sécurité côté client, nous recommandons d’activer la whitelist IP. L’anti‑replay HMAC côté client API est optionnel (non obligatoire).
Headers envoyés:
- X-Webhook-Timestamp
- X-Webhook-Signature = HMAC SHA256 de
timestamp.payload
La signature garantit que le webhook vient bien de MoroccoTopup. Utilisez le secret API pour la vérifier.
Important: votre endpoint doit répondre en 200 (2xx) pour que le webhook soit considéré comme reçu. Sans réponse 2xx, l’envoi sera retenté.
Politique de retry: 6 tentatives, backoff progressif (60s, 5m, 15m, 30m, 1h, 2h).
Événements envoyés:
- recharge.created
- recharge.status_changed
We send a webhook on every status change. It lets you sync your systems in real time.
You can set the webhook URL:
- from the Admin UI (API Accounts > Webhook URL)
- via the API:
POST /webhook
For extra security on your side, we recommend enabling IP whitelist. Client‑side anti‑replay HMAC is optional (not required).
Headers:
- X-Webhook-Timestamp
- X-Webhook-Signature = HMAC SHA256 of
timestamp.payload
The signature ensures the webhook is genuine. Use your API secret to verify it.
Important: your endpoint must return 200 (2xx) so the webhook is considered received. Without a 2xx response, it will be retried.
Retry policy: 6 attempts, progressive backoff (60s, 5m, 15m, 30m, 1h, 2h).
Events sent:
- recharge.created
- recharge.status_changed
Enviamos un webhook en cada cambio de estado. Permite sincronizar tus sistemas en tiempo real.
Puedes configurar la URL del webhook:
- desde la interfaz Admin (Cuentas API > Webhook URL)
- vía API:
POST /webhook
Para mayor seguridad, recomendamos activar la lista blanca de IP. El anti‑replay HMAC del lado del cliente es opcional (no requerido).
Headers:
- X-Webhook-Timestamp
- X-Webhook-Signature = HMAC SHA256 de
timestamp.payload
La firma asegura que el webhook es legítimo. Usa tu secreto API para validarla.
Importante: tu endpoint debe responder 200 (2xx) para que el webhook se considere recibido. Si no hay respuesta 2xx, se reintentará.
Política de reintentos: 6 intentos, backoff progresivo (60s, 5m, 15m, 30m, 1h, 2h).
Eventos enviados:
- recharge.created
- recharge.status_changed
Payload webhook Webhook payload Payload del webhook
{
"event": "recharge.status_changed",
"client_code": "API-20251226-569",
"status_code_old": 1,
"status_code_new": 2,
"recharge": {
"ref_interne": "189957125550360",
"external_reference": "TEST-001",
"phone": "0612345678",
"operator": "Inwi",
"value": 10,
"type": "6",
"billing_amount": 9.35,
"created_at": "2026-01-09 19:21:57",
"executed_at": "2026-01-09 19:22:00"
}
}
Recevoir + vérifier + exploiter Receive + verify + process Recibir + verificar + procesar
<?php
$secret = 'YOUR_HMAC_SECRET';
$raw = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $timestamp . '.' . $raw, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
$payload = json_decode($raw, true);
$event = $payload['event'] ?? '';
$statusCode = $payload['status_code_new'] ?? null;
$externalRef = $payload['recharge']['external_reference'] ?? null;
// Exemple: mettre à jour votre recharge en base (par external_reference)
// updateRechargeStatus($externalRef, $statusCode, $payload);
http_response_code(200);
echo json_encode(['ok' => true]);
app.post('/webhook',
express.json({ verify: (req, res, buf) => { req.rawBody = buf.toString(); } }),
(req, res) => {
const timestamp = req.header('X-Webhook-Timestamp');
const signature = req.header('X-Webhook-Signature');
const expected = crypto.createHmac('sha256', secret)
.update(`${timestamp}.${req.rawBody}`)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return res.status(401).send('Invalid signature');
}
const event = req.body.event;
const statusCode = req.body.status_code_new;
const externalRef = req.body.recharge?.external_reference;
// Exemple: mettre à jour votre recharge en base (par external_reference)
// await updateRechargeStatus(externalRef, statusCode, req.body);
res.json({ ok: true });
}
);
@PostMapping("/webhook")
public ResponseEntity<String> webhook(@RequestBody String raw, HttpServletRequest req) throws Exception {
String secret = "YOUR_HMAC_SECRET";
String timestamp = req.getHeader("X-Webhook-Timestamp");
String signature = req.getHeader("X-Webhook-Signature");
String expected = hmacSha256(timestamp + "." + raw, secret);
if (!constantTimeEquals(expected, signature)) {
return ResponseEntity.status(401).body("Invalid signature");
}
// Exemple: mettre à jour votre recharge en base (par external_reference)
// updateRechargeStatus(externalRef, statusCode, payload);
return ResponseEntity.ok("{\"ok\":true}");
}
| Code Code Código | HTTP | Description Description Descripción |
|---|---|---|
| INSUFFICIENT_FUNDS | 400 | Solde insuffisant. Insufficient balance. Saldo insuficiente. |
| CLIENT_SUSPENDED | 403 | Compte suspendu. Account suspended. Cuenta suspendida. |
| IP_NOT_ALLOWED | 403 | IP non autorisée. IP not allowed. IP no permitida. |
| AUTH_MISSING | 401 | Identifiants API manquants. Missing API credentials. Credenciales API faltantes. |
| AUTH_INVALID | 401 | Identifiants API invalides. Invalid API credentials. Credenciales API inválidas. |
| DUPLICATE_REFERENCE | 409 | Référence externe déjà utilisée. External reference already used. Referencia externa ya utilizada. |
| DAILY_LIMIT_EXCEEDED | 400 | Limite quotidienne atteinte. Daily limit reached. Límite diario alcanzado. |
| NOT_FOUND | 404 | Recharge introuvable. Recharge not found. Recarga no encontrada. |
| WEBHOOK_URL_REQUIRED | 400 | webhook_url est requis. webhook_url is required. webhook_url es obligatorio. |
| WEBHOOK_URL_INVALID | 400 | webhook_url invalide. Invalid webhook_url. webhook_url inválido. |
| WEBHOOK_UPDATE_FAILED | 500 | Mise à jour webhook impossible. Webhook update failed. Error al actualizar webhook. |
D'autres erreurs de validation peuvent apparaître (ex: Operateur invalide. Valeurs autorisées: Orange, Inwi, Maroc Telecom. ou Missing or invalid payload (phone, operator, value).). Other validation errors may appear (e.g. Operateur invalide. Valeurs autorisées: Orange, Inwi, Maroc Telecom. or Missing or invalid payload (phone, operator, value).). Pueden aparecer otros errores de validación (p. ej. Operateur invalide. Valeurs autorisées: Orange, Inwi, Maroc Telecom. o Missing or invalid payload (phone, operator, value).).
- Toujours renseigner external_reference pour éviter les doublons. Always set external_reference to avoid duplicates. Siempre define external_reference para evitar duplicados.
-
En cas de timeout, revérifier via
/recharge/by-refavant de relancer. On timeout, check/recharge/by-refbefore retrying. Si hay timeout, consulta/recharge/by-refantes de reintentar. - Pour les webhooks, répondre 200 rapidement et traiter en asynchrone. For webhooks, respond 200 quickly and process asynchronously. Para webhooks, responde 200 rápido y procesa en segundo plano.
- Stocker la correspondance status_code → libellé côté client. Store the status_code → label mapping on your side. Guarda el mapeo status_code → etiqueta en tu lado.
- ref_interne — référence MoroccoTopup.MoroccoTopup reference.referencia MoroccoTopup.
- external_reference — référence client.client reference.referencia del cliente.
- value — valeur faciale de la recharge.face value.valor nominal.
- billing_amount — montant facturé.billed amount.importe facturado.
- type — promo de recharge (ex: 6 => promo *6).recharge promo (ex: 6 => promo *6).promo de recarga (ej: 6 => promo *6).