Sous-marchands par l'API : créditer le wallet d'un sous-marchand et suivre ses webhooks
Sous-marchand et API : créer une session qui crédite un wallet avec config.settlement, suivre wallet.activated et les reversements, traiter les erreurs.
Pour encaisser au profit d'un sous-marchand par l'API, créez une session de paiement comme pour tout encaissement et ajoutez dans config l'objet settlement, qui désigne le wallet à créditer — par son walletId ou par la subMerchantReference du sous-marchand. L'acheteur paie sur le checkout hébergé, et votre système suit le reste par webhooks signés : wallet.activated et wallet.rejected pour la vérification, payout.completed et payout.failed pour les reversements, submerchant.near_cap pour les plafonds. Ce guide donne la requête, les événements, la déclaration de l'endpoint, les erreurs à prévoir, ce qui relève du portail ou de l'API, et le rapprochement par sous-marchand.
Sous-marchand et API : wallet, walletId et subMerchantReference
ChariPay est une plateforme de paiement marocaine, un PSP opéré par Chari Money, établissement de paiement agréé par Bank Al-Maghrib. Son espace distribution s'adresse aux agrégateurs, franchises et réseaux d'agents. Un sous-marchand est une entité que vous créez dans votre espace distribution, un vendeur ou un point de vente par exemple ; il est vérifié comme tout marchand — documents et registre de commerce — avant d'encaisser en production, et dispose de son wallet dédié.
Côté API, quatre notions suffisent :
| Terme | Ce que c'est | Format |
|---|---|---|
| Sous-marchand | Entité créée dans votre espace distribution, vérifiée comme tout marchand | Déclaré depuis le portail ou par l'API |
| Wallet | Le wallet dédié du sous-marchand, crédité par les sessions qui le désignent | Un par sous-marchand |
walletId | Identifiant du wallet du sous-marchand à créditer | UUID, par exemple 3fa85f64-5717-4562-b3fc-2c963f66afa6 |
subMerchantReference | Référence du sous-marchand à créditer | Chaîne, par exemple subm_9Zt2Bc |
Les deux identifiants se placent dans config.settlement, un objet optionnel de la session : envoyez l'un ou l'autre, celui que votre base conserve déjà. Cet objet ne sert qu'à désigner le wallet d'un sous-marchand à créditer.
La déclaration d'un sous-marchand se fait depuis le portail ou par l'API ; le contrat public de l'API ne détaille pas cet appel, qui se met en place avec l'équipe d'intégration. L'offre, elle, est décrite sur la page distribution et sous-marchands, et le modèle commercial d'une plateforme multi-vendeurs dans paiements d'une place de marché.
Créer une session qui crédite un sous-marchand
La requête est celle d'une session de paiement classique, avec un bloc settlement en plus dans config :
curl -X POST 'https://api-psp.charipay.ma/v1/payment-sessions' \
-H 'X-CHARI-PAY-API-KEY: chari_sk_test_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: order-2026-0421-session' \
-d '{
"amount": 250.00,
"orderId": "ORD-2026-0421",
"externalId": "order-2026-0421",
"config": {
"customer": {
"email": "amine.bennani@example.com",
"firstName": "Amine",
"lastName": "Bennani",
"phone": "+212600000000"
},
"urls": {
"accept": "https://votre-boutique.ma/paiement/succes",
"decline": "https://votre-boutique.ma/paiement/echec",
"notification": "https://votre-boutique.ma/webhooks/charipay"
},
"settlement": {
"subMerchantReference": "subm_9Zt2Bc"
}
},
"metadata": { "cartId": "c_987", "source": "web" }
}'La réponse (201, ou 200 au rejeu sur externalId) contient ces cinq champs et rien d'autre :
{
"sessionId": "ps_5Kd0Rn",
"sessionToken": "st_…",
"verifyKey": "vk_5Kd0Rn",
"expiresAt": "2026-10-05T10:15:00Z",
"checkoutUrl": "https://…/checkout/ps_5Kd0Rn"
}Redirigez l'acheteur vers checkoutUrl : il paie par carte avec 3-D Secure sur la page hébergée. La réponse ne renvoie ni statut, ni montant, ni sous-marchand : le statut et le montant se lisent par GET /v1/payment-sessions/{sessionId}, et c'est à votre base de conserver le lien entre sessionId et sous-marchand. Chaque session a une durée de vie limitée — 72 heures par défaut, expiresAt pour la raccourcir — et n'est consommée qu'une seule fois. Confirmez la commande sur réception du webhook, jamais sur la seule redirection. Les champs et réponses sont détaillés dans le module « Sessions de paiement » de la documentation.
Vous pouvez écrire ce premier appel dès aujourd'hui : Commencer en mode test. Trois champs — nom, e-mail professionnel, entreprise —, un lien d'activation reçu par e-mail pour choisir votre mot de passe, puis le portail, où vous créez vous-même votre clé de test après une ré-authentification ; la clé complète ne s'affiche qu'une seule fois. Payez avec la carte de test 4918 9141 0719 5005, CVV 123, n'importe quelle date d'expiration future, code 3-D Secure 555. Vous créez ainsi des sessions de checkout tout de suite ; le montage de la distribution — et donc les sous-marchands que désigne config.settlement — se prépare avec l'équipe d'intégration.
Les événements du cycle de vie d'un sous-marchand
Cinq événements de webhook concernent la distribution. Leur nom dit ce qu'ils signalent ; la réaction est à vous :
| Événement | Ce qu'il signale | Réaction proposée |
|---|---|---|
wallet.activated | Le wallet d'un sous-marchand est activé à l'issue de la vérification | Passer le sous-marchand en « actif » dans votre base et ouvrir ses ventes |
wallet.rejected | Le wallet d'un sous-marchand est rejeté à la vérification | Bloquer ses ventes, le prévenir, reprendre son dossier |
payout.completed | Un reversement a été exécuté | Marquer le reversement comme exécuté dans votre comptabilité |
payout.failed | Un reversement a échoué | Alerter l'équipe finance et analyser avant toute nouvelle demande |
submerchant.near_cap | Un sous-marchand approche de son plafond de solde | Prévenir le responsable du point de vente ou l'équipe finance |
Les paiements eux-mêmes arrivent par payment.succeeded. Routez toujours sur l'en-tête Chari-Event-Type, et lisez l'identifiant de l'événement dans Chari-Event-Id. Dédupliquez sur Chari-Event-Id, pas sur Chari-Webhook-Id — ce dernier identifie la livraison, pas l'événement. Vérifiez la signature X-CHARI-SIGNATURE avec X-CHARI-TIMESTAMP avant tout traitement, et ne bâtissez rien sur les champs du corps au-delà de votre metadata. Le catalogue et les règles de livraison sont dans le module « Webhooks » de la documentation.
Déclarer l'endpoint qui reçoit ces événements
Un endpoint de webhooks se déclare par POST /api/v1/partner/webhooks/endpoints, avec la clé de l'environnement visé. Abonnez-le à une liste explicite d'événements : une liste vide vaut abonnement à tous les événements, présents et futurs.
curl -X POST 'https://api-psp.charipay.ma/api/v1/partner/webhooks/endpoints' \
-H 'X-CHARI-PAY-API-KEY: chari_sk_test_...' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://votre-boutique.ma/webhooks/charipay",
"description": "Paiements boutique",
"enabledEvents": [
"payment.succeeded",
"wallet.activated",
"wallet.rejected",
"payout.completed",
"payout.failed",
"submerchant.near_cap"
]
}'Trois règles à connaître :
- l'
urldoit être en HTTPS public, sur le port 443 :localhost, les adresses privées et le HTTP sont refusés dès l'enregistrement — en développement, passez par un tunnel ; - la réponse porte le
signingSecret(whsec_…) une seule fois, à la création ou à la rotation : rangez-le aussitôt dans un coffre ; - un endpoint appartient à un environnement, et les secrets diffèrent entre sandbox et production.
La même déclaration peut se faire depuis le portail. Pour la mise au point en local — tunnel, événement de test, journal des livraisons —, voyez tester ses webhooks en local.
Les erreurs à prévoir
Toutes les erreurs de l'API /v1 partagent la même enveloppe :
{
"error": {
"code": "VALIDATION_ERROR",
"message": "amount: must be greater than 0"
},
"correlationId": "b0c1e2d3-4f56-7890-abcd-ef0123456789"
}Testez le code, jamais le message, et journalisez le correlationId. Les codes utiles pour ce parcours :
| Statut | Code | Ce qu'il signifie | Que faire |
|---|---|---|---|
| 400 | VALIDATION_ERROR | La requête est mal formée ou n'a pas passé la validation | Corriger la requête avant de la renvoyer ; ne pas la rejouer telle quelle |
| 401 | UNAUTHORIZED | Clé d'API absente ou invalide | Vérifier l'en-tête X-CHARI-PAY-API-KEY et l'environnement de la clé |
| 403 | PRODUCTION_ACCESS_NOT_ENABLED | Clé chari_sk_live_… utilisée avant l'activation de la production | Rester en mode test jusqu'à l'activation |
| 409 | IDEMPOTENCY_CONFLICT | Même Idempotency-Key, corps différent | Générer une nouvelle clé pour une nouvelle requête |
| 422 | WALLET_NOT_ACTIVE | Requête valide, mais une règle métier la bloque | Ne pas rediriger l'acheteur ; journaliser et faire le point avec l'équipe d'intégration si le code persiste |
Le contrat donne WALLET_NOT_ACTIVE comme exemple générique de code 422, sur la plupart de ses endpoints : ne lui prêtez pas de cause plus précise que son nom. Rappelez-vous aussi qu'un rejeu sur un externalId existant renvoie 200 OK au lieu de 201 Created : les deux sont des succès, testez le statut.
Ce qui se fait dans le portail, ce qui se fait par l'API
| Opération | Où elle se fait | Ce que votre système en voit |
|---|---|---|
| Déclarer un sous-marchand, un par un ou par lot | Portail ou API | Le sous-marchand et son wallet dédié |
| Vérifier le sous-marchand | Intégrée : documents et registre de commerce | wallet.activated ou wallet.rejected |
| Encaisser pour un sous-marchand | API : session avec config.settlement | payment.succeeded, avec votre metadata |
| Recevoir les événements | Endpoint déclaré par l'API ou depuis le portail | En-têtes Chari-Event-Type et Chari-Event-Id |
| Reverser | Reversements à double validation : demandeur et approbateur distincts | payout.completed ou payout.failed |
| Plafonner le solde | Plafonds de solde définis au montage | submerchant.near_cap |
Tout le reste — répartition, fréquence de la collecte automatique, organisation des approbateurs — se prépare avec l'équipe d'intégration. Retenez la frontière : pour la distribution, le contrat public de l'API couvre la session avec config.settlement et les événements ; le reste suit la gouvernance fixée au montage.
Rapprocher les encaissements par sous-marchand
La réponse de création ne nomme pas le sous-marchand, et aucun champ non documenté du corps de l'événement ne doit servir à le retrouver. Rendez donc chaque paiement autoporteur : ajoutez à votre metadata la référence du sous-marchand — par exemple "subMerchantReference": "subm_9Zt2Bc" à côté de cartId. Votre metadata est renvoyé tel quel dans l'événement de paiement, et l'événement d'une session porte aussi votre orderId. Retrouvez votre commande par ces deux valeurs : l'externalId d'une session ne revient pas dans l'événement.
Deux règles de construction suffisent :
- 1Enregistrez, au moment de créer la session, la ligne
sessionId→ sous-marchand → commande dans votre base. - 2À la réception de
payment.succeeded, retrouvez cette ligne par votremetadataet marquez la commande payée.
Après une indisponibilité de votre serveur, réconciliez par GET /v1/transactions : le sondage est un repli, pas la source de vérité. Un relevé mensuel est aussi établi pour chaque sous-marchand, limité à sa propre activité.
Passer en production avec un réseau de sous-marchands
Une fois le KYB validé — documents d'identité et registre de commerce, avec double validation humaine —, votre dossier est étudié et vous recevez une proposition chiffrée, puis votre compte est activé et votre clé de production créée. Côté code, vous ne changez que la clé et le secret de webhook : les URL, les payloads et les codes d'erreur sont identiques. Tant que la production n'est pas activée, une clé chari_sk_live_… reçoit 403 PRODUCTION_ACCESS_NOT_ENABLED. Gardez la sandbox comme environnement de recette, avec deux jeux de variables d'environnement.
Pour la distribution, le montage — sous-marchands, plafonds, collecte, approbateurs — se prépare avec l'équipe d'intégration ; nous ne promettons rien ici sur son comportement en sandbox. Pour le reste de l'API — liens de paiement, remboursements, transactions —, voyez API de paiement au Maroc.
Questions fréquentes
Quelle différence entre walletId et subMerchantReference ?
Les deux désignent la destination d'un paiement dans config.settlement. walletId est l'identifiant du wallet du sous-marchand, au format UUID ; subMerchantReference est la référence du sous-marchand, par exemple subm_9Zt2Bc. Envoyez l'un ou l'autre, celui que votre base conserve déjà. Le bloc settlement est optionnel : il ne sert qu'à désigner le wallet d'un sous-marchand. Les deux champs servant le même but, choisissez-en un et tenez-vous-y dans tous vos appels : vos rapprochements n'en seront que plus simples.
Peut-on déclarer un sous-marchand par l'API ?
Oui : la déclaration se fait depuis le portail ou par l'API, un par un ou par lot. Le contrat public de l'API ne détaille pas cet appel ; il se met en place avec l'équipe d'intégration, au moment du montage de la distribution. Dans tous les cas, le sous-marchand est vérifié comme tout marchand — documents et registre de commerce — avant d'encaisser en production.
Que faire si la création d'une session renvoie une erreur 422 ?
Ne redirigez pas l'acheteur. Un 422 signifie que la requête est valide mais qu'une règle métier la bloque ; le contrat cite WALLET_NOT_ACTIVE comme exemple, sans que son nom en dise davantage. Lisez le code, journalisez le correlationId, vérifiez dans votre base l'état du sous-marchand désigné, et faites le point avec l'équipe d'intégration si le code persiste.
Quels webhooks suivre pour un réseau de sous-marchands ?
payment.succeeded pour les paiements, puis les cinq événements de la distribution : wallet.activated et wallet.rejected pour la vérification, payout.completed et payout.failed pour les reversements, submerchant.near_cap pour les plafonds. Abonnez votre endpoint à cette liste explicite plutôt qu'à tous les événements, routez sur Chari-Event-Type et dédupliquez sur Chari-Event-Id. Vérifiez la signature avant tout traitement, et ne bâtissez rien sur les champs du corps au-delà de votre metadata.
Comment savoir qu'un sous-marchand a été validé ?
Par l'événement wallet.activated, qui signale l'activation de son wallet à l'issue de la vérification ; wallet.rejected signale au contraire un rejet. Nous ne publions pas de durée de vérification. Branchez ces deux événements sur le statut du sous-marchand dans votre base, et n'ouvrez ses ventes en production qu'après l'activation. D'ici là, gardez le sous-marchand en statut « en vérification » dans votre plateforme.
La réponse de création de session indique-t-elle le sous-marchand crédité ?
Non. La réponse contient cinq champs et rien d'autre : sessionId, sessionToken, verifyKey, expiresAt et checkoutUrl. Le statut et le montant se lisent ensuite par GET /v1/payment-sessions/{sessionId}. Conservez vous-même le lien entre sessionId et sous-marchand, et ajoutez sa référence à votre metadata, renvoyé tel quel dans l'événement de paiement : vous retrouverez ainsi le sous-marchand dans chaque événement de paiement, sans recherche manuelle.
Comment retrouver les paiements d'un sous-marchand ?
Par votre propre base, alimentée par les événements : mettez la référence du sous-marchand dans votre metadata, renvoyé tel quel dans l'événement de paiement, et l'événement d'une session porte aussi votre orderId. Après une indisponibilité, réconciliez par GET /v1/transactions. Un relevé mensuel est en outre établi pour chaque sous-marchand, limité à sa propre activité.
Prochaine étape
Écrivez vos sessions et votre récepteur de webhooks contre la sandbox : Commencer en mode test, puis payez avec la carte de test. En parallèle, préparez le montage de la distribution avec l'équipe d'intégration, en partant de la page distribution et sous-marchands et du module « Sessions de paiement » de la documentation.
Écrit par Équipe ChariPay.