v1
Dashboard

Documentation API Faymaco

Gérez les abonnements mensuels de vos clients via Faymaco : collecte de paiement par WhatsApp (Wave / Orange Money), relances automatiques, et notifications en temps réel par webhook signé.

Vous créez un abonnement ; Faymaco envoie la demande de paiement, relance en cas d'impayé, encaisse, et vous notifie à chaque paiement. Vous n'avez ni à planifier les envois ni à gérer l'encaissement — vous réagissez simplement au webhook. Accès disponible à partir du plan Pro.

Authentification

Chaque requête s'authentifie avec une clé API secrète envoyée dans l'en-tête Authorization. Créez et gérez vos clés depuis votre dashboard Faymaco, page Développeurs.

  • Clés de production fk_live_… et de test fk_test_….
  • La clé n'est affichée qu'une seule fois, à la création — stockez-la en lieu sûr.
  • Une clé révoquée renvoie 401. Ne l'exposez jamais côté client.
En-tête d'authentification
Authorization: Bearer fk_live_xxxxxxxxxxxxxxxxxxxx

Environnements

Deux environnements, mêmes endpoints. Utilisez une clé fk_test_ sur le test et fk_live_ en production.

EnvironnementBase URL
Productionhttps://apifayko.peelo.chat/api/v1
Testhttps://playground.fayma.co/api/v1
Base URL
https://apifayko.peelo.chat/api/v1

Démarrage rapide

Créez votre premier abonnement en une requête.

Idempotency-Key — optionnel mais recommandé. C'est vous (le client) qui générez cette valeur, unique par opération (votre identifiant de commande, ou crypto.randomUUID()). Si vous renvoyez la même clé après un timeout/réessai, Faymaco rejoue la réponse initiale au lieu de créer un 2e abonnement. Sans cet en-tête, la requête est traitée normalement. Validité : 24 h.
Requête — cURL
curl -X POST https://apifayko.peelo.chat/api/v1/subscriptions \
  -H "Authorization: Bearer fk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: cmd-12345" \
  -d '{
    "customer": { "name": "Awa Diop", "phone": "+221770000000" },
    "amount": 5000, "currency": "XOF", "frequency": "monthly",
    "webhooks": {
      "onSuccess": "https://votre-app.com/paiement-ok",
      "onExpired": "https://votre-app.com/impaye"
    }
  }'
Requête — JavaScript
const res = await fetch("https://apifayko.peelo.chat/api/v1/subscriptions", {
  method: "POST",
  headers: {
    "Authorization": "Bearer fk_live_...",
    "Content-Type": "application/json",
    "Idempotency-Key": "cmd-12345"
  },
  body: JSON.stringify({
    customer: { name: "Awa Diop", phone: "+221770000000" },
    amount: 5000, currency: "XOF", frequency: "monthly",
    webhooks: { onSuccess: "https://votre-app.com/paiement-ok",
                onExpired: "https://votre-app.com/impaye" }
  })
});
const data = await res.json();

Créer un abonnement

POST /v1/subscriptions
Idempotent

Crée un abonnement récurrent pour un client.

Paramètres du corps

CléTypeDescription
customer.namestring requisNom du client.
customer.phonestring requisNuméro WhatsApp au format international (+221…).
amountnumber requisMontant par cycle.
frequencystring requismonthly · quarterly · semi_annual · annual
currencystringDéfaut XOF.
startDateISO dateDate du 1er cycle. Défaut : maintenant.
startNextMonthbooleantrue → 1er cycle le 1er du mois suivant.
webhooks.onSuccessurlURL notifiée à chaque paiement réussi.
webhooks.onExpiredurlURL notifiée quand l'échéance passe sans paiement.
cURL
curl -X POST .../v1/subscriptions \
  -H "Authorization: Bearer fk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "customer": { "name": "Awa Diop",
                  "phone": "+221770000000" },
    "amount": 5000,
    "frequency": "monthly"
  }'
JavaScript
await fetch(base + "/subscriptions", {
  method: "POST",
  headers: { Authorization: `Bearer ${key}`,
             "Content-Type": "application/json" },
  body: JSON.stringify({
    customer: { name: "Awa Diop",
                phone: "+221770000000" },
    amount: 5000, frequency: "monthly"
  })
});
Réponse · 201
{
  "success": true,
  "data": {
    "subscription": {
      "id": "6a33...",
      "status": "active",
      "customer": { "name": "Awa Diop",
                     "phone": "+221770000000" },
      "pricing": { "amount": 5000,
                    "currency": "XOF",
                    "frequency": "monthly" },
      "nextDueDate": "2026-07-01T00:00:00Z",
      "cycleCount": 0
    }
  }
}

Lister les abonnements

GET /v1/subscriptions

Paramètres de requête

CléTypeDescription
statusstringFiltre : active, paused, cancelled
limitnumber1–100. Défaut 20.
cursorstringCurseur de pagination (voir nextCursor).

La réponse contient data.pagination.nextCursor (ou null). Passez-le en cursor pour la page suivante.

cURL
curl ".../v1/subscriptions?status=active&limit=20" \
  -H "Authorization: Bearer fk_live_..."
JavaScript
await fetch(base + "/subscriptions?status=active", {
  headers: { Authorization: `Bearer ${key}` }
});

Récupérer un abonnement

GET /v1/subscriptions/:id

Renvoie le détail d'un abonnement par son identifiant.

cURL
curl .../v1/subscriptions/6a33... \
  -H "Authorization: Bearer fk_live_..."
JavaScript
await fetch(base + `/subscriptions/${id}`, {
  headers: { Authorization: `Bearer ${key}` }
});

Pause · Reprise · Annulation

POST /v1/subscriptions/:id/pause

POST /v1/subscriptions/:id/resume

POST /v1/subscriptions/:id/cancel

Pilotez le cycle de vie d'un abonnement. Une annulation est définitive et arrête les relances en cours.

cURL
curl -X POST .../v1/subscriptions/6a33.../pause \
  -H "Authorization: Bearer fk_live_..."
JavaScript
await fetch(base + `/subscriptions/${id}/pause`, {
  method: "POST",
  headers: { Authorization: `Bearer ${key}` }
});

Cycle de vie & timing

Quand votre client est-il sollicité ? Cela dépend des champs fournis à la création :

À la création1er message
aucun startDateEnvoyé automatiquement, dans la minute.
startDate futureEnvoyé à cette date.
startNextMonth: trueEnvoyé le 1er du mois suivant.

Déroulé d'un cycle, entièrement géré par Faymaco :

  1. À l'échéance, envoi d'une demande de paiement WhatsApp (+ facture PDF).
  2. Si impayé : relances automatiques (par défaut J+1, J+3, J+7).
  3. Au paiement, déclenchement du webhook subscription.payment.succeeded.
  4. L'échéance avance d'une période et le cycle suivant repart.
Différer le 1er message
{
  "customer": { ... },
  "amount": 5000,
  "frequency": "monthly",
  "startNextMonth": true
}
Vous n'avez rien à planifier ni à encaisser : créez l'abonnement, Faymaco gère l'envoi, les relances et la collecte.

Webhooks — événements

Faymaco appelle votre URL webhooks.onSuccess en POST à chaque événement.

ÉvénementQuand
subscription.payment.succeededUn cycle a été payé.
subscription.payment.failedL'échéance est passée sans paiement (cycle impayé après ~7 jours) → envoyé sur onExpired.

En-têtes envoyés

En-têteValeur
X-Faymaco-Eventnom de l'événement
X-Faymaco-Timestampunix (secondes)
X-Faymaco-Signaturet=<ts>,v1=<hmac>
Corps reçu
{
  "id": "evt_xxx",
  "event": "subscription.payment.succeeded",
  "created": "2026-07-01T09:00:00Z",
  "data": {
    "subscriptionId": "6a33...",
    "cycleNumber": 1,
    "paidAt": "2026-07-01T09:00:00Z",
    "customer": { "name": "Awa Diop",
                   "phone": "+221770000000" },
    "pricing": { "amount": 5000,
                  "currency": "XOF" }
  }
}

Vérifier la signature

Chaque webhook est signé : vérifiez la signature pour garantir qu'il vient bien de Faymaco et que le corps n'a pas été modifié. La signature v1 = HMAC_SHA256(secret, "<timestamp>.<corps brut>").

Le secret est celui affiché sur votre page Développeurs (bouton « afficher ») — le même que GET /api/fayko/api-keys/webhook-secret. Il est partagé (jamais transmis dans la requête) et peut être régénéré en cas de fuite.

Retries — en cas de réponse non-2xx ou de timeout (10s), Faymaco réessaie jusqu'à 3 fois (backoff ~2s puis ~10s). Répondez 2xx rapidement et traitez en asynchrone.
Vérification — Node.js
const crypto = require("crypto");

function verify(req, secret) {
  const m = /t=(\d+),v1=([0-9a-f]+)/
            .exec(req.headers["x-faymaco-signature"]);
  if (!m) return false;
  const [, ts, v1] = m;
  // anti-rejeu : refuser si trop ancien (> 5 min)
  if (Math.abs(Date.now()/1000 - Number(ts)) > 300)
    return false;
  const exp = crypto.createHmac("sha256", secret)
    .update(`${ts}.${req.rawBody}`).digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(exp), Buffer.from(v1));
}

Codes d'erreur

Toutes les erreurs suivent la forme { "success": false, "error": { "code", "message" } }.

HTTPcodeSens
401NO_API_KEY / INVALID_API_KEYClé absente, invalide ou révoquée.
403FEATURE_NOT_AVAILABLEPlan sans accès API (Pro+ requis).
403QUOTA_EXCEEDEDQuota mensuel de demandes atteint — création refusée (details: { used, limit, plan }).
403ACCOUNT_SUSPENDEDCompte suspendu.
400VALIDATION_ERRORCorps de requête invalide.
400INVALID_STATETransition de statut impossible.
404SUBSCRIPTION_NOT_FOUNDAbonnement introuvable.
409IDEMPOTENCY_IN_PROGRESSRequête identique en cours.
429RATE_LIMITEDTrop de requêtes — réessayez après Retry-After.
Exemple d'erreur · 403
{
  "success": false,
  "error": {
    "code": "FEATURE_NOT_AVAILABLE",
    "message": "Plan Pro requis."
  }
}