Intégration des marchands
Le cycle de vie d'une connexion et ce qui est figé dès son acceptation par un marchand.
Une connexion possède deux statuts qui évoluent indépendamment. status indique si le marchand a
accepté la connexion. payment_status indique si elle peut déplacer de l'argent. Une connexion live
peut être active tout en refusant tous les paiements. C'est la surprise la plus fréquente de cette
section.
Le cycle de vie
status | Signification | Origine |
|---|---|---|
pending | Créée, personne ne l'a acceptée | POST /accounts |
active | Un marchand s'est connecté et l'a autorisée | Il a ouvert le lien d'autorisation |
cancelled | Arrêtée par vous | DELETE /accounts/{id} |
expired | L'invitation a expiré avant son acceptation | Le temps |
Il n'existe ni statut restricted ni état intermédiaire. Une connexion annulée renvoie
cancelled, une connexion associée à un marchand renvoie active, une invitation que personne n'a
acceptée avant sa date renvoie expired, et toutes les autres renvoient pending. expires_at est la date de l'invitation : elle est effacée quand un marchand
accepte, et une connexion acceptée n'expire jamais.
Le lien d'autorisation
La création d'une connexion génère un jeton d'autorisation à usage unique et le renvoie dans une URL.
Le marchand ouvre le lien, se connecte à son propre compte Wajub, consulte les capacités demandées, puis accepte. Votre plateforme ne manipule jamais ses identifiants et ne crée rien de son côté.
Le lien est un identifiant d'accès
Toute personne qui possède cette URL peut associer un compte à votre plateforme. Envoyez-la par
un canal que vous contrôlez et traitez-la comme un secret. Si elle est divulguée,
POST /accounts/{id}/token révoque tous les liens déjà émis, y compris celui d'un rappel, et en
génère un nouveau, valable de nouveau 30 jours. Cet appel est refusé
avec 400 dès qu'un marchand a revendiqué la connexion, qu'elle soit encore active ou déjà
déconnectée, car ce marchand y reste rattaché et le nouveau lien ne pourrait pas servir. Pour le
joindre à nouveau, créez une nouvelle connexion qu'il accepte à nouveau.
Une invitation ne reste pas ouverte indéfiniment : elle expire 30 jours après la dernière émission de
son lien, à la création de la connexion, par POST /accounts/{id}/token ou par Renvoyer
l'invitation dans le Dashboard, chacun relançant les 30 jours. Les deux derniers révoquent les liens
déjà envoyés. Trois jours avant, un rappel part à l'adresse du marchand si vous l'avez indiquée,
avec email sur POST /accounts ou PUT /accounts/{id} ; elle est conservée dans profile.email.
Le rappel porte son propre lien, et celui que vous avez déjà envoyé continue de fonctionner. Une fois
l'invitation expirée, tous les liens qui dorment dans la boîte du marchand cessent de fonctionner.
Une connexion Relay n'a ni invitation, ni lien d'autorisation (authorization_url vaut null), et
n'expire jamais.
Vous pouvez avoir au plus 50 invitations ouvertes par environnement : des connexions créées et ni
acceptées, ni annulées, ni expirées. Les connexions Relay ne comptent pas, puisqu'aucun marchand ne
les accepte. Au-delà, POST /accounts répond 429 avec You have reached the maximum of 50 pending invitations. Annulez les invitations dont vous n'avez plus besoin, ou laissez-les expirer.
Vous ne voyez authorization_url qu'une fois, dans la réponse à l'appel qui l'a généré : la création
du sous-compte, ou la régénération de son jeton. Lire le sous-compte ensuite ne le renvoie jamais,
revendiqué ou non. Conservez-le dès que vous le recevez ; si vous le perdez, générez-en un nouveau.
Une fois la connexion revendiquée, connected_merchant apparaît dans la réponse avec le nom et l'adresse e-mail
de l'entreprise du marchand.
Ce que le lien ne permet pas
Une connexion ne se revendique qu'une fois, et les contrôles ont lieu au moment de la revendication, pas à l'ouverture du lien par le marchand :
- une connexion que vous avez annulée reste annulée, et le lien ne peut pas la réactiver.
POST /accounts/{id}/token, lui, le peut : sur une invitation annulée que personne n'a revendiquée, il génère un nouveau lien et remet la connexion àpending, pour 30 jours de nouveau ; - une connexion déjà revendiquée par un autre marchand ne peut pas être reprise ;
- une invitation expirée est refusée ;
- en mode live, le marchand ne peut pas la revendiquer avant d'avoir déposé son dossier KYC. Le dépôt suffit pour se connecter ; c'est la vérification qui débloque les paiements (voir plus bas) ;
- le lien peut porter un
redirect_uri, mais seulement s'il partage son origine avec lecallbackenregistré sur la connexion. Tout autre hôte est refusé, et une connexion sans callback n'en accepte aucun.
Un marchand qui valide deux fois arrive sur sa propre connexion terminée, et non sur une erreur.
Retour sur votre page
Avant d'envoyer le lien, vous pouvez y ajouter &state=… (une valeur à vous, par exemple un jeton
aléatoire lié à la session du vendeur) et &redirect_uri=…. Une fois la connexion acceptée, le
marchand est envoyé vers ce redirect_uri, ou vers le callback de la connexion si le lien n'en
porte pas, avec ces paramètres de requête :
| Paramètre | Ce qu'il porte |
|---|---|
account | L'id de la connexion (acc_…), celui de chaque appel et de chaque événement account.* |
state | Le state que vous avez mis sur le lien d'autorisation, renvoyé tel quel. Comparez-le à celui que vous avez conservé, pour savoir que la visite répond à votre propre lien |
status | connected |
https://example.com/connected?account=acc_7Yh2MpL4tRb3nP8sZcXv&state=8f3a&status=connectedLe callback doit être une adresse HTTPS : POST et PUT /accounts répondent 422 sinon, sauf
http://localhost et http://127.0.0.1 avec une clé sandbox.
Ce retour sert le navigateur du marchand, ce n'est pas une preuve : n'importe qui peut ouvrir cette
adresse. Confirmez la connexion avec GET /accounts/{account} ou attendez account.connected sur
votre endpoint de webhook avant d'agir.
Activer ensuite les paiements
L'acceptation ne donne pas toujours l'autorisation de débiter. payment_status est fixé au moment où
le marchand accepte.
| Le marchand accepte | payment_status |
|---|---|
| En sandbox | active immédiatement |
| En live, vérification déjà validée | active immédiatement, à condition qu'une connexion lite ait aussi accès au rail Wajub |
| En live, vérification encore en cours | inactive |
Une connexion inactive passe seule à active lorsque la vérification du marchand est validée et,
pour une connexion lite, une fois le rail Wajub accordé. Wajub envoie alors
account.payment_activated.
payment_status | Effet sur les requêtes qui contiennent X-Sync |
|---|---|
inactive | Appels qui déplacent de l'argent refusés avec 403 ; lectures exécutées |
active | Tout ce que permettent les capacités s'exécute |
suspended | Appels qui déplacent de l'argent refusés avec 403 ; lectures exécutées |
Ce qui est refusé tant que payment_status n'est pas active : créer, activer, modifier ou débiter
un paiement, créer ou modifier un lien de paiement, un remboursement, un transfert ou un lot,
ajouter, modifier ou vérifier un bénéficiaire ou enregistrer ce qu'un vendeur doit rendre, et
accepter ou clore un litige. Ce qui fonctionne encore : tous les GET, pour suivre ce que fait la
connexion pendant l'examen de la vérification du marchand, ainsi qu'annuler un paiement, supprimer un
lien de paiement ou un bénéficiaire, et les recherches d'identité (/identity/resolve,
/identity/validate).
Vous pouvez ensuite utiliser la suspension. Définir payment_status sur suspended arrête en même
temps les paiements entrants et les transferts sortants. Ce comportement est volontaire : une
connexion figée après l'échec d'un nouveau contrôle de conformité ne doit pas pouvoir vider son
solde alors qu'elle ne peut plus recevoir d'argent.
L'interrupteur ne lève qu'une suspension que vous avez posée vous-même. Repasser payment_status à
active répond 409 avec error_code: merchant_not_verified tant que la vérification du marchand
est en cours ou a été refusée : ces connexions reviennent d'elles-mêmes une fois la vérification
validée.
Le portail du vendeur Lite
Un vendeur lite n'a pas de dashboard marchand : c'est tout l'objet du mode. Son onboarding se
termine sur la demande d'accès au rail Wajub, et il utilise ensuite un portail qui lui est propre,
à vos couleurs.
| Ce qu'il y fait | Comment |
|---|---|
| Se connecter | Un code à usage unique envoyé à l'adresse de la boutique, sans mot de passe, avec une session courte |
| Voir ses ventes et ses versements | Uniquement ceux de cette connexion : jamais son autre activité, jamais celle d'une autre plateforme |
| Demander l'accès au rail Wajub | Étape hébergée de l'onboarding ; la connexion ne peut pas encaisser tant qu'elle n'est pas approuvée |
| Déclarer où va son argent | La page des coordonnées de versement du portail |
Une nouvelle destination de versement est mise en attente
Une destination que le vendeur vient de déclarer ou de modifier ne peut recevoir aucun versement pendant un délai de carence ; le portail indique au vendeur à partir de quand elle peut être payée. Un transfert vers une destination en attente est refusé jusqu'à la fin de ce délai.
Ce que le marchand doit accepter à nouveau
Dès l'acceptation du marchand, deux des quatre champs modifiables cessent de dépendre de vous seul.
| Champ | Après l'acceptation |
|---|---|
capabilities | 202 Accepted : proposé au marchand, renvoyé dans pending_changes |
pricing | 202 Accepted : proposé au marchand, renvoyé dans pending_changes |
callback | Reste librement modifiable |
payment_status | Reste librement modifiable, et ne l'est qu'à partir de ce moment |
Cette asymétrie est volontaire. Ce que le marchand a accepté sur l'écran d'autorisation ne peut pas être modifié à son insu : une connexion élargie ou une commission relevée devient une proposition qu'il accepte ou refuse depuis son dashboard, la connexion continuant entre-temps aux conditions en vigueur. La suspension, elle, reste la vôtre et ne se propose pas.
De son côté, le marchand retrouve toutes les plateformes auxquelles il est connecté sous Settings,
Connected apps : les capacités accordées, la commission acceptée, l'état des paiements et toute
proposition en attente. Accepter ou refuser une proposition depuis cette page vous envoie
account.change_accepted ou account.change_declined. La même page lui permet de se déconnecter de
votre plateforme, ce qui met fin à la connexion exactement comme votre propre DELETE.
Mettre fin à une connexion
DELETE /accounts/{id} révoque les jetons, définit status sur cancelled et émet
account.deauthorized. Si le marchand avait accepté, payment_status passe également à inactive
et la réponse contient Account disconnected. Pour une invitation non revendiquée, elle contient
Account cancelled.
curl -X DELETE https://api.wajub.com/accounts/acc_7Yh2MpL4tRb3nP8sZcXv \
-H "Authorization: sk.xxxxxxxxxxxxxxxx…"La déconnexion ne modifie rien de ce qui s'est déjà produit. Le marchand conserve son compte, ses paiements et son solde. Les commissions déjà acquises restent acquises. Seule votre capacité à agir en son nom prend fin.
Les événements à écouter
Le cycle de vie d'une connexion est décrit par les événements ci-dessous, sur les endpoints de votre
plateforme. Créer une connexion envoie account.created, avec le compte mais sans son lien :
authorization_url ne figure que dans la réponse à POST /accounts. Le
catalogue des événements les décrit tous.
| Événement | Moment de l'envoi |
|---|---|
account.created | Vous la créez avec POST /accounts |
account.onboarding_started | Le marchand ouvre le lien d'autorisation |
account.onboarding_email_verified | Il confirme son adresse e-mail |
account.onboarding_account_selected | Il choisit le compte Wajub à connecter |
account.onboarding_kyc_submitted | Il dépose son KYC pendant l'onboarding |
account.connected | Il accepte la connexion |
account.payment_activated | La connexion obtient la capacité de débiter |
account.payment_suspended | Elle est suspendue, par vous ou après l'échec d'une vérification |
account.change_requested | Vous proposez de nouvelles capacités ou une nouvelle tarification |
account.change_accepted | Le marchand accepte une proposition |
account.change_declined | Le marchand refuse une proposition |
account.updated | Elle change : par PUT /accounts/{id} (ni une proposition seule, ni un appel qui ne change rien), quand POST /accounts/{id}/token remet une invitation annulée en pending, ou quand Wajub envoie le versement d'un marchand Lite (last_payout_at) |
account.commission_receivable_recorded | Une commission est due sur une vente encaissée hors de Wajub |
account.commission_receivable_collected | Cette commission est recouvrée |
account.deauthorized | La connexion est déconnectée ou annulée |
Deux formats, deux signatures
Les événements qui suivent vos propres appels à l'API (POST /accounts, PUT et
DELETE /accounts/{id}, POST /accounts/{id}/token, versements Lite, demandes de modification,
commissions dues) portent l'enveloppe habituelle, X-Wajub-Signature et X-Wajub-Timestamp. Les
événements des étapes du marchand lui-même (onboarding_*, connected, change_accepted,
change_declined, les activations qui suivent une vérification, et une déconnexion faite dans le
Dashboard) portent un en-tête X-Sync-Event et un corps à plat avec event, account (l'id
acc_…, aussi dans sync_uid pour les intégrations plus anciennes), sandbox, timestamp et
les champs propres à l'événement. Ils sont signés dans X-Sync-Signature-V1 et
X-Sync-Timestamp, de la même façon que les autres : v1= et le HMAC-SHA256 en hexadécimal de
{timestamp}.{raw_body} avec le secret de signature de votre endpoint. Passez ces deux en-têtes à
constructEvent du SDK, qui vérifie la signature et refuse un horodatage vieux de plus de 300
secondes. Conservez ensuite le X-Idempotency-Key de chaque livraison traitée et ignorez celle
que vous avez déjà vue. Ils portent aussi X-Sync-Signature, le HMAC-SHA256 en hexadécimal du
seul corps brut, conservé pour les intégrations qui le vérifient aujourd'hui et appelé à être
retiré plus tard : ne bâtissez rien dessus. Ces événements n'atteignent qu'un endpoint abonné à leur nom exact, pas un endpoint abonné à *, et
bénéficient de trois tentatives : immédiatement, puis 30 secondes et 2 minutes plus tard.
De plus, chaque paiement, remboursement et transfert effectué sur un compte connecté, et chaque litige ouvert sur l'un de ses paiements, déclenche son webhook normal pour le marchand et une copie pour votre plateforme. Vous n'avez pas besoin d'interroger régulièrement une connexion pour connaître son activité.