API Recharge Recharge API API de recargas

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

Production Production Producción
https://api.moroccotopup.com/rest/V1/recharge-api
Sandbox
https://api.moroccotopup.com/rest/V1/recharge-api/sandbox
3 éléments nécessaires 3 required elements 3 elementos requeridos
  • 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).

Headers requis: X-Client-Code + X-Api-Key Required headers: X-Client-Code + X-Api-Key Headers requeridos: X-Client-Code + X-Api-Key
Authentification Authentication Autenticación

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.

Conventions & formats Conventions & formats Convenciones y formatos

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" }
}
Sandbox (test) Sandbox (testing) Sandbox (pruebas)

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.
Endpoints v1 Endpoints v1 Endpoints v1
GET

/me

Informations du compte API. API account information. Información de la cuenta API.

GET

/balance

Solde + devise + limite de crédit. Balance + currency + credit limit. Saldo + divisa + límite de crédito.

GET

/prices

Grille tarifaire par opérateur. Price list per operator. Lista de precios por operador.

POST

/recharge

Recharge unitaire. Single recharge. Recarga unitaria.

POST

/recharge/bulk

Recharge multiple. Bulk recharge. Recarga múltiple.

GET

/recharge/by-ref/:externalReference

Recherche par référence externe. Lookup by external reference. Búsqueda por referencia externa.

GET

/recharge/by-id/:id

Recherche par référence interne. Lookup by internal reference. Búsqueda por referencia interna.

GET

/history

Historique paginé. Paginated history. Historial paginado.

GET

/webhook

Lire la config webhook. Read webhook config. Leer configuración de webhook.

POST

/webhook

Mettre à jour la config webhook. Update webhook config. Actualizar configuración de webhook.

GET

/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.

Exemple — Recharge unitaire Example — Single recharge Ejemplo — Recarga unitaria
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
}
Détails des endpoints Endpoint details Detalles de endpoints

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"
}
Statuts (status_code) Statuses (status_code) Estados (status_code)

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
1File d'attente
2En cours d'exécution
3Effectué
4Cette ligne est inexistante
5Montant de la recharge est incorrect
6Opération échouée
7Le numéro introduit n'est pas disponible
8Erreur interne
9Problème interne
10L'offre n'est pas valide
11Remboursement du solde
12Le numéro <numero> est suspendu
16Tentative finale en attente de confirmation
17En cours de vérification
19Solde WhatsApp delivered
20Convertie en solde WhatsApp
21Préparation de la deuxième tentative
22Deuxième tentative
0Inconnu
Webhooks Webhooks Webhooks

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}");
}
Erreurs fréquentes Common errors Errores comunes
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).).

Bonnes pratiques Best practices Buenas prácticas
  • 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-ref avant de relancer. On timeout, check /recharge/by-ref before retrying. Si hay timeout, consulta /recharge/by-ref antes 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.
Glossaire Glossary Glosario
  • ref_interneréférence MoroccoTopup.MoroccoTopup reference.referencia MoroccoTopup.
  • external_referenceréférence client.client reference.referencia del cliente.
  • valuevaleur faciale de la recharge.face value.valor nominal.
  • billing_amountmontant facturé.billed amount.importe facturado.
  • typepromo de recharge (ex: 6 => promo *6).recharge promo (ex: 6 => promo *6).promo de recarga (ej: 6 => promo *6).