Valsoria Pay Console →
Documentation

Démarrer

IntroductionDémarrageAuthentification

API

PaiementsPage de paiementDécaissementsRelevé de compteWebhooksRéférenceErreurs

Outils

Numéros de testSDK JavaScriptVersions

Encaisser son premier paiement

Ce guide part de zéro et s'arrête quand vous avez encaissé, puis reçu la confirmation. Comptez une vingtaine de minutes.

Où en est l'API. Encaissement, page de paiement, décaissement, lots de décaissement et demandes de règlement répondent aujourd'hui.

Les bénéficiaires se LISENT par API, ils ne s'y créent pas. GET /v1-beneficiaries vous donne les identifiants dont /v1-settlements a besoin, pour que vous n'ayez pas à les relever à la main. Mais une destination s'enregistre depuis votre console : une clé compromise ne doit pas pouvoir ajouter la sienne puis s'y faire régler. Nous le disons plutôt que de vous le laisser découvrir sur un 405.

Le remboursement ne se demande pas par l'API : la décision revient à LibrePay, sur justification. Écrivez à contact@avasoftware.net avec l'identifiant du paiement ; vous recevrez payment.refunded ou payment.partially_refunded sur vos webhooks, exactement comme si vous l'aviez demandé. Le code refund_via_support est expliqué dans le catalogue des erreurs.

La référence de l'API ne décrit que ce qui répond aujourd'hui.

1. Vos clés

Dans la console marchand, onglet Compte. Une clé secrète vp_sk_test_… s'affiche une seule fois : elle n'est stockée que hachée chez nous, et nous ne pourrons pas vous la redonner.

La clé détermine l'environnement. Il n'y a aucun paramètre de mode : vp_sk_test_… ne touche jamais d'argent réel, vp_sk_live_… ne touche jamais de données de test, et rien ne traverse la frontière. C'est volontaire — le paramètre de mode est la façon la plus courante de débiter un vrai client en croyant tester.

export VALSORIA_CLE="vp_sk_test_…"
export VALSORIA_BASE="https://qkmvexkljdcfarulydao.supabase.co/functions/v1"

2. Trois conventions à connaître avant d'écrire une ligne

Les montants sont des entiers, en unité mineure. Le franc CFA n'a pas de subdivision : 10000 vaut 10 000 F. Un montant en chaîne ("10000") est refusé en invalid_amount, et c'est délibéré — deviner l'intention derrière un montant est le début des ennuis.

Idempotency-Key est obligatoire sur tout appel qui déplace de l'argent. Rejouez la même clé avec le même corps : vous recevez la réponse d'origine, statut compris, et aucun second mouvement n'a lieu. La clé doit venir de votre commande (cmd-2026-00412), pas d'un uuid() régénéré à chaque tentative — sinon vous générez une clé neuve par tentative et l'idempotence ne vous protège de rien.

Une réponse 201 ne veut pas dire que l'argent est arrivé. Elle dit qu'un paiement existe. Le statut réel suit le cycle created → pending → succeeded | failed, et vous l'apprenez par webhook.

3. Encaisser

curl -X POST "$VALSORIA_BASE/v1-payments" \
  -H "Authorization: Bearer $VALSORIA_CLE" \
  -H "Idempotency-Key: cmd-2026-00412" \
  -H "Content-Type: application/json" \
  -d '{
        "amount": 10000,
        "currency": "XOF",
        "operator": "orange_ci",
        "counterparty": { "msisdn": "0779149021" },
        "merchant_reference": "CMD-2026-00412"
      }'
{ "id": "txn_6INMt2lIL1Pv_y6RgqZvag", "object": "payment", "amount": 10000,
  "currency": "XOF", "status": "created", "livemode": false, … }

merchant_reference est unique par marchand : une seconde transaction portant la même valeur est refusée en 409 duplicate_reference. C'est une protection contre le double encaissement, pas une contrainte administrative.

En sandbox, ajoutez "simulate": "succeed" pour obtenir un paiement réussi immédiatement. Pour éprouver les refus et les pannes, utilisez plutôt les numéros de test : ils traversent toute la chaîne, webhook compris.

4. Recevoir le résultat

Déclarez une URL d'endpoint dans la console. Le détail complet est sur la page Webhooks. Chaque événement arrive signé :

LibrePay-Signature: t=1755770000,v1=<hmac-sha256 hexadécimal>

La signature porte sur t + "." + corps, pas sur le corps seul : sans l'horodatage dans le message signé, un ancien appel authentique pourrait être rejoué en changeant simplement le t affiché.

Trois règles, dans l'ordre d'importance :

  1. Vérifiez la signature avant de lire le contenu. Un endpoint de webhook est une URL publique ; sans vérification, n'importe qui vous annonce un paiement réussi.
  2. Signez sur le corps BRUT, tel qu'il est arrivé. Un corps re-sérialisé après JSON.parse change d'un octet — ordre des clés, espaces — et la signature, elle, porte sur les octets reçus. C'est la première cause d'échec de vérification.
  3. Dédupliquez sur id. La livraison est « au moins une fois » : un même événement peut arriver deux fois, et un retard réseau suffit.

Répondez 2xx dès que l'événement est enregistré, pas quand il est traité. Nous réessayons sur tout autre code, avec un délai croissant.

5. Les remboursements arrivent quand même chez vous

Vous ne les demandez pas — mais vous les recevez. Quand LibrePay traite un remboursement, votre paiement passe refunded ou partially_refunded, et les webhooks payment.refunded / payment.partially_refunded vous parviennent comme les autres. Traitez-les : ne pas les traiter laisserait votre système croire qu'un paiement remboursé est toujours acquis.

6. Quand ça ne marche pas

Toute réponse porte LibrePay-Request-Id. Il apparaît à l'identique dans Journal API de votre console : méthode, route, code HTTP, latence, et le code d'erreur exact. Commencez toujours par là — la réponse à « est-ce moi ou vous ? » s'y trouve sans nous écrire. Si vous nous écrivez, citez cet identifiant : il désigne votre requête, et elle seule.

Les erreurs ont toutes la même forme :

{ "error": { "code": "invalid_amount", "message": "…" } }

Testez le code, jamais le message. Le code est stable ; le message est rédigé pour un humain et peut être reformulé.

Et ensuite

Version d'API 2026-08-25 · changements · spécification OpenAPI