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 testfk_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.
Authorization: Bearer fk_live_xxxxxxxxxxxxxxxxxxxx
Environnements
Deux environnements, mêmes endpoints. Utilisez une clé fk_test_ sur le test et fk_live_ en production.
| Environnement | Base URL |
|---|---|
| Production | https://apifayko.peelo.chat/api/v1 |
| Test | https://playground.fayma.co/api/v1 |
https://apifayko.peelo.chat/api/v1
Démarrage rapide
Créez votre premier abonnement en une requête.
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.
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" } }'
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
Crée un abonnement récurrent pour un client.
Paramètres du corps
| Clé | Type | Description |
|---|---|---|
| customer.name | string requis | Nom du client. |
| customer.phone | string requis | Numéro WhatsApp au format international (+221…). |
| amount | number requis | Montant par cycle. |
| frequency | string requis | monthly · quarterly · semi_annual · annual |
| currency | string | Défaut XOF. |
| startDate | ISO date | Date du 1er cycle. Défaut : maintenant. |
| startNextMonth | boolean | true → 1er cycle le 1er du mois suivant. |
| webhooks.onSuccess | url | URL notifiée à chaque paiement réussi. |
| webhooks.onExpired | url | URL notifiée quand l'échéance passe sans paiement. |
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" }'
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" }) });
{
"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
Paramètres de requête
| Clé | Type | Description |
|---|---|---|
| status | string | Filtre : active, paused, cancelled… |
| limit | number | 1–100. Défaut 20. |
| cursor | string | Curseur de pagination (voir nextCursor). |
La réponse contient data.pagination.nextCursor (ou null). Passez-le en cursor pour la page suivante.
curl ".../v1/subscriptions?status=active&limit=20" \ -H "Authorization: Bearer fk_live_..."
await fetch(base + "/subscriptions?status=active", { headers: { Authorization: `Bearer ${key}` } });
Récupérer un abonnement
Renvoie le détail d'un abonnement par son identifiant.
curl .../v1/subscriptions/6a33... \ -H "Authorization: Bearer fk_live_..."
await fetch(base + `/subscriptions/${id}`, { headers: { Authorization: `Bearer ${key}` } });
Pause · Reprise · Annulation
Pilotez le cycle de vie d'un abonnement. Une annulation est définitive et arrête les relances en cours.
curl -X POST .../v1/subscriptions/6a33.../pause \ -H "Authorization: Bearer fk_live_..."
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éation | 1er message |
|---|---|
aucun startDate | Envoyé automatiquement, dans la minute. |
startDate future | Envoyé à cette date. |
startNextMonth: true | Envoyé le 1er du mois suivant. |
Déroulé d'un cycle, entièrement géré par Faymaco :
- À l'échéance, envoi d'une demande de paiement WhatsApp (+ facture PDF).
- Si impayé : relances automatiques (par défaut J+1, J+3, J+7).
- Au paiement, déclenchement du webhook
subscription.payment.succeeded. - L'échéance avance d'une période et le cycle suivant repart.
{
"customer": { ... },
"amount": 5000,
"frequency": "monthly",
"startNextMonth": true
}
Webhooks — événements
Faymaco appelle votre URL webhooks.onSuccess en POST à chaque événement.
| Événement | Quand |
|---|---|
| subscription.payment.succeeded | Un cycle a été payé. |
| subscription.payment.failed | L'échéance est passée sans paiement (cycle impayé après ~7 jours) → envoyé sur onExpired. |
En-têtes envoyés
| En-tête | Valeur |
|---|---|
| X-Faymaco-Event | nom de l'événement |
| X-Faymaco-Timestamp | unix (secondes) |
| X-Faymaco-Signature | t=<ts>,v1=<hmac> |
{
"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.
2xx rapidement et traitez en asynchrone.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" } }.
| HTTP | code | Sens |
|---|---|---|
| 401 | NO_API_KEY / INVALID_API_KEY | Clé absente, invalide ou révoquée. |
| 403 | FEATURE_NOT_AVAILABLE | Plan sans accès API (Pro+ requis). |
| 403 | QUOTA_EXCEEDED | Quota mensuel de demandes atteint — création refusée (details: { used, limit, plan }). |
| 403 | ACCOUNT_SUSPENDED | Compte suspendu. |
| 400 | VALIDATION_ERROR | Corps de requête invalide. |
| 400 | INVALID_STATE | Transition de statut impossible. |
| 404 | SUBSCRIPTION_NOT_FOUND | Abonnement introuvable. |
| 409 | IDEMPOTENCY_IN_PROGRESS | Requête identique en cours. |
| 429 | RATE_LIMITED | Trop de requêtes — réessayez après Retry-After. |
{
"success": false,
"error": {
"code": "FEATURE_NOT_AVAILABLE",
"message": "Plan Pro requis."
}
}