Documentation développeur

API REST et SDK JavaScript pour initier des paiements, déclencher des payouts et recevoir des webhooks signés HMAC-SHA256. Environnements sandbox et production séparés.

Base URL : https://axiapayhub.com

Démarrage rapide

API REST server-to-server, sans redirection. Votre back-end appellePOST /api/public/v1/payins, puis /confirm avec le numéro et l'opérateur saisis dans votre formulaire. Le client reste sur votre site.

curl -X POST https://axiapayhub.com/api/public/v1/payins \
  -H "Authorization: Bearer axp_sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{"amount": 15000, "currency": "XOF", "country": "SN", "reference": "cmd-4821"}'

Authentification

Deux paires de clés — une par environnement — disponibles depuis /business → Clés API. La clé publique identifie votre compte côté client. La clé secrète autorise les appels serveur viaAuthorization: Bearer <clé secrète> et ne doit jamais être exposée dans un bundle front-end.

  • axp_pk_test_… / axp_sk_test_… — sandbox, sans flux réel.
  • axp_pk_live_… / axp_sk_live_… — production, activée après validation KYB.

L'API est REST : utilisez curl, PHP, Python, Node ou tout client HTTP. Le SDK JavaScript est optionnel.

# Lister les opérateurs mobile money d'un pays (endpoint public, sans clé)
curl "https://axiapayhub.com/api/public/v1/operators?country=SN&service=payin"

# Réponse
# {
#   "country": "SN",
#   "service": "payin",
#   "operators": [
#     { "name": "Orange Money", "slug": "orange", "otp_required": true, "ussd_code": "#144#391*CODE#" },
#     { "name": "Wave", "slug": "wave", "otp_required": false, "ussd_code": null }
#   ]
# }

Payin (server-to-server, sans redirection)

Disponible pour tous les comptes (particulier KYC ou entreprise KYB) une fois le pays activé. Vous hébergez votre propre formulaire de paiement sur votre domaine — aucune redirection vers AxiaPayHub. Flux en deux étapes :

1. Client sur votre site
2. Client saisit numéro + opérateur (chez VOUS, aucune redirection)
3. Votre back-end → POST /api/public/v1/payins puis /confirm
4. Client reçoit la notification USSD sur son téléphone (AxiaPayHub → SebPay)
5. Client valide sur son téléphone (USSD ou code OTP)
6. Webhook payin.paye signé HMAC-SHA256 sur votre endpoint
7. Le client reste sur votre site — vous affichez la confirmation
Pré-requis (sinon 403) : compte vérifié (KYC particulier ou KYB entreprise), pays activé par un admin AxiaPayHub, opérateur activé pour le service payin. Consultez GET /operators?country=SN.
  1. Créer une sessionPOST /api/public/v1/payins renvoie sessionId, la grille des frais, les totaux (clientPays, youReceive, afterFees) et la liste des opérateurs disponibles pour le pays.
  2. ConfirmerPOST /api/public/v1/payins/{sessionId}/confirm avec phone, operator et (si requiresOtp) otp.
Décomposition des montants (à afficher au payeur)
fees.amount — frais AxiaPayHub calculés au taux du pays (fees.percent, 6% à 17% selon le pays)
totals.clientPaysTotal à payer par le client (montant + frais)
totals.youReceive — Montant brut de la commande
totals.afterFees — Montant net crédité sur votre wallet AxiaPayHub
Affichez ce récapitulatif (Montant / Frais / Total à payer / Reçu marchand) sur votre propre écran de paiement avant l'appel /confirm.

Statut final par webhook payin.paye / payin.echoue. Vous pouvez aussi relire le statut via GET /api/public/v1/payins/{id} ou GET /api/public/v1/payins/{id}/status avec sessionId, votre reference ou le transactionId SebPay.

# 1) Créer la session
curl -X POST https://axiapayhub.com/api/public/v1/payins \
  -H "Authorization: Bearer axp_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 15000, "currency": "XOF", "country": "SN",
    "reference": "cmd-4821",
    "description": "Formation TikTok Ads",
    "metadata": { "userId": "user-123" },
    "customer": { "email": "client@example.com", "phone": "+221701234567" },
    "ttlSeconds": 3600
  }'

# 2) Confirmer avec les infos saisies dans VOTRE formulaire
curl -X POST https://axiapayhub.com/api/public/v1/payins/payin_xxx/confirm \
  -H "Authorization: Bearer axp_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+221701234567", "operator": "orange" }'

# Étape OTP si status === "otp_required"
curl -X POST https://axiapayhub.com/api/public/v1/payins/payin_xxx/confirm \
  -H "Authorization: Bearer axp_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+221701234567", "operator": "orange", "otp": "482913" }'

Payout instantané

Réservé aux comptes entreprise KYB validés. Le nom et prénom du bénéficiaire sont obligatoires (conformité). Retourne 202 Accepted — le statut final arrive par webhook.

const payout = await axia.payouts.send({
  amount: 50000, currency: "XOF", country: "CI",
  operator: "orange",
  beneficiary: {
    firstName: "Awa",
    lastName: "Diallo",
    phone: "+2250701234567",
  },
  description: "Commission vendeur",
});
Réponse 202
{
  "id": "b0d1…",
  "externalReference": "axp_out_9c1a…",
  "transactionId": "sebpay_tx_xxx",
  "status": "processing",
  "amount": 50000, "currency": "XOF", "country": "CI",
  "env": "production"
}

GET status

Utilisez ces endpoints pour synchroniser votre back-end si un webhook est en retard ou si votre client revient sur une page de confirmation. La clé secrète limite la réponse à vos propres transactions et à son environnement (_test_ ou _live_).

# Payin : recherche par sessionId, reference marchand ou transactionId SebPay
curl https://axiapayhub.com/api/public/v1/payins/cmd-4821/status   -H "Authorization: Bearer axp_sk_live_..."

# Payout : recherche par id, externalReference ou transactionId SebPay
curl https://axiapayhub.com/api/public/v1/payouts/axp_out_9c1a.../status   -H "Authorization: Bearer axp_sk_live_..."
Réponse payin
{
  "sessionId": "payin_9c1a...",
  "status": "paye",
  "amount": 15000,
  "currency": "XOF",
  "country": "SN",
  "reference": "cmd-4821",
  "operator": "orange",
  "payer": { "phone": "221701234567", "msisdn": "221701234567", "operator": "orange", "country": "SN" },
  "customer": { "email": "client@example.com", "phone": "+221701234567" },
  "metadata": { "userId": "user-123" },
  "transactionId": "sebpay_tx_xxx",
  "fees": 900,
  "netCredited": 14100,
  "env": "production",
  "paidAt": "2026-07-16T10:12:34.000Z"
}
Réponse payout
{
  "id": "b0d1...",
  "status": "reussi",
  "amount": 50000,
  "currency": "XOF",
  "country": "CI",
  "operator": "orange",
  "fees": 3000,
  "totalDebit": 53000,
  "beneficiary": { "firstName": "Awa", "lastName": "Diallo", "phone": "+2250701234567" },
  "externalReference": "axp_out_9c1a...",
  "transactionId": "sebpay_tx_yyy",
  "env": "production"
}

Opérateurs par pays & OTP

Utilisez le slug exact ci-dessous dans le champ operator des appels/payins et /payouts. La liste est chargée en direct depuisGET /api/public/v1/operators?country=XX&service=payin|payout. Un opérateur marqué OTP requis demande un code de validation à saisir par le payeur.

OpérateurSlugOTPUSSD
Aucun opérateur disponible pour ce pays / service.

Lorsque otp = requis, l'appel POST /api/public/v1/payins/{sessionId}/confirmrenvoie status: "otp_required" et un ussdCode à afficher au client ; renvoyez ensuite la même requête avec le champ otp saisi par le payeur.

Webhooks HMAC

Chaque endpoint enregistré depuis /business → Webhooksreçoit son propre secret HMAC-SHA256. L'en-tête X-AxiaPay-Signature est calculé sur le corps brut de la requête. Vérifiez-le avant tout traitement.

Content-Type: application/json
X-AxiaPay-Signature: sha256=<hex>
X-Axia-Signature: sha256=<hex> (alias compatible)
X-Axia-Event: payin.paye | payin.echoue | payout.reussi | payout.echoue
X-Axia-Env: sandbox | production
X-Axia-Delivery: evt_xxx
payin.paye
{
  "id": "evt_abc123",
  "type": "payin.paye",
  "created_at": "2026-07-16T10:12:34.000Z",
  "env": "production",
  "data": {
    "sessionId": "payin_9c1a…",
    "reference": "cmd-4821",
    "amount": 15000, "currency": "XOF", "country": "SN",
    "fees": 900, "netCredited": 14100,
    "operator": "orange",
    "transactionId": "sebpay_tx_xxx",
    "payer": {
      "phone": "221701234567",
      "msisdn": "221701234567",
      "operator": "orange",
      "country": "SN"
    },
    "customer": {
      "email": "client@example.com",
      "phone": "+221701234567"
    },
    "metadata": { "userId": "user-123" }
  }
}
payin.echoue
{
  "id": "evt_abc124",
  "type": "payin.echoue",
  "created_at": "2026-07-16T10:12:34.000Z",
  "env": "production",
  "data": {
    "sessionId": "payin_9c1a…",
    "reference": "cmd-4821",
    "reason": "rejected_by_provider",
    "payer": { "phone": "221701234567", "operator": "orange", "country": "SN" },
    "customer": { "email": "client@example.com", "phone": "+221701234567" },
    "metadata": { "userId": "user-123" }
  }
}
payout.reussi
{
  "id": "evt_abc125",
  "type": "payout.reussi",
  "created_at": "2026-07-16T10:12:34.000Z",
  "env": "production",
  "data": {
    "id": "b0d1…",
    "externalReference": "axp_out_9c1a…",
    "amount": 50000, "currency": "XOF", "country": "CI",
    "operator": "orange",
    "transactionId": "sebpay_tx_yyy"
  }
}
payout.echoue
{
  "id": "evt_abc126",
  "type": "payout.echoue",
  "created_at": "2026-07-16T10:12:34.000Z",
  "env": "production",
  "data": {
    "id": "b0d1…",
    "externalReference": "axp_out_9c1a…",
    "reason": "rejected_by_provider"
  }
}
import crypto from "node:crypto";
import express from "express";

const app = express();
app.use(express.raw({ type: "application/json" }));

app.post("/webhooks/axia", (req, res) => {
  const raw = req.body.toString("utf8");
  const signature = req.header("x-axia-signature") ?? "";
  const expected = crypto
    .createHmac("sha256", process.env.AXIA_WEBHOOK_SECRET)
    .update(raw)
    .digest("hex");
  const ok = crypto.timingSafeEqual(
    Buffer.from(signature.replace(/^sha256=/, "")),
    Buffer.from(expected),
  );
  if (!ok) return res.status(401).send("invalid signature");

  const event = JSON.parse(raw);
  switch (event.type) {
    case "payin.paye":    /* créditer la commande event.data.reference */ break;
    case "payin.echoue":  /* marquer la commande en échec */ break;
    case "payout.reussi": /* marquer le payout comme envoyé */ break;
    case "payout.echoue": /* replanifier ou notifier */ break;
  }
  res.status(200).send("ok");
});

Spec OpenAPI 3.0

Spec générée depuis le code qui sert les routes — elle ne peut pas se désynchroniser. Importable dans Postman, Insomnia ou Swagger UI.

Endpoint : GET https://axiapayhub.com/api/public/v1/openapi.json

Sandbox et production

  • Les clés _test_ et _live_ pointent sur des flux séparés.
  • Les webhooks portent le champ env (sandbox ou production).
  • Les journaux /business → Logs filtrent par environnement.

Format d'erreur

Toutes les erreurs sont JSON :

{
  "error": "Unknown API key",
  "code": "invalid_api_key"
}

Codes courants : missing_api_key, invalid_api_key, server_error,insufficient_balance (422 — solde du wallet insuffisant pour un payout),provider_validation (422 — opérateur / numéro / OTP rejeté par SebPay),provider_error (502 — erreur remontée par SebPay), plus les erreurs de validation Zod avec le détail du champ.