📍 Lecture connexe : Stripe, Paystack, Flutterwave et Wave en 2026 : intégrer un processeur de paiement — pour la vue d’ensemble du paysage paiement.
Une marketplace ou une plateforme SaaS qui orchestre plusieurs vendeurs a un problème spécifique : encaisser pour le compte de tiers, leur reverser leurs gains, prélever une commission, et déléguer la conformité KYC à quelqu’un de plus compétent que soi. Stripe Connect Express résout cette équation en hébergeant l’onboarding du vendeur et en gérant le KYC dans une centaine de pays. Ce tutoriel construit pas à pas une intégration complète, du compte de plateforme jusqu’à la première transaction encaissée et redistribuée.
Prérequis
- Node.js 22 LTS et npm 10 ou supérieur, vérifier avec
node --version - Un compte Stripe en mode test, créé sur dashboard.stripe.com/register
- Stripe Connect activé dans le tableau de bord (Settings → Connect → Get started)
- Niveau attendu : intermédiaire — vous avez déjà fait au moins une intégration de paiement basique
- Temps estimé : environ 90 minutes pour parcourir l’ensemble du tutoriel
Étape 1 — Comprendre les types de comptes Connect
Avant d’écrire la moindre ligne, il faut choisir le type de compte connecté. Stripe en propose trois : standard, express et custom. La différence porte sur la quantité de responsabilités que la plateforme prend à sa charge versus celles déléguées à Stripe.
En mode standard, le vendeur a un compte Stripe pleinement autonome ; il peut se connecter au tableau de bord Stripe, voir ses transactions, gérer son équipe. La plateforme se contente de l’agréger. C’est le plus simple à intégrer mais le moins « marketplace ».
En mode express, la plateforme est responsable du flux d’expérience mais Stripe héberge l’onboarding (formulaire de KYC, vérification d’identité, ajout du RIB). Le vendeur a un dashboard Express simplifié dans Stripe. C’est le mode recommandé en 2026 pour la majorité des marketplaces.
En mode custom, la plateforme gère tout le flux UX et appelle l’API Stripe pour pousser les informations KYC. C’est puissant mais exige beaucoup d’ingénierie et un audit annuel plus contraignant.
On choisit express pour ce tutoriel car il offre le meilleur équilibre coût d’intégration / professionnalisme du résultat. La plateforme gère l’inscription du vendeur sur son propre site, puis redirige vers un onboarding hébergé Stripe pour la conformité.
Étape 2 — Initialiser le projet Node.js
On démarre une API Express minimale. Cette API expose deux endpoints au front-end : un pour créer un compte vendeur, un pour générer le lien d’onboarding.
mkdir marketplace-stripe && cd marketplace-stripe
npm init -y
npm install express stripe dotenv
npm install -D typescript @types/node @types/express tsx
npx tsc --init
Le SDK officiel stripe est mis à jour à chaque release de l’API. On vérifie ensuite la version installée avec npm list stripe et on consulte le changelog du SDK pour confirmer la compatibilité avec la version d’API choisie.
On crée un fichier .env à la racine pour les clés. Les clés de test commencent par sk_test_ côté serveur et pk_test_ côté client. Elles sont visibles dans Settings → Developers → API keys du tableau de bord Stripe.
# .env
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
PLATFORM_URL=http://localhost:3000
Ne jamais commiter ce fichier. On ajoute .env dans .gitignore dès maintenant. Une fuite de clé secrète dans un dépôt public déclenche une révocation automatique par Stripe en quelques minutes, mais entre-temps un attaquant peut effectuer des appels.
Étape 3 — Créer un compte connecté Express
L’endpoint POST /accounts de l’API Stripe crée le compte vendeur. À ce stade, le compte existe dans Stripe mais n’est pas encore opérationnel : il manque l’onboarding KYC. On code une route /api/sellers qui prend en entrée l’email du futur vendeur et retourne l’identifiant de compte Stripe créé.
// src/server.ts
import express from 'express'
import Stripe from 'stripe'
import 'dotenv/config'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2026-04-22.dahlia', // figer la version au déploiement
})
const app = express()
app.use(express.json())
app.post('/api/sellers', async (req, res) => {
const { email } = req.body
const account = await stripe.accounts.create({
type: 'express',
email,
country: 'FR', // pays d'enregistrement du vendeur
capabilities: {
card_payments: { requested: true },
transfers: { requested: true },
},
})
// Stocker account.id en base, lié au user de la plateforme
res.json({ accountId: account.id })
})
app.listen(3000, () => console.log('listening on 3000'))
Quelques détails comptent. La propriété apiVersion doit être figée à la version de l’API utilisée au moment du déploiement (les versions stables suivent un nommage par fleur : 2025-11-17.clover, 2026-04-22.dahlia) : laisser Stripe choisir automatiquement expose à des ruptures silencieuses lors des changements de version. La clé capabilities active les fonctionnalités attendues : card_payments pour accepter les cartes, transfers pour recevoir des fonds depuis la plateforme. Sans ces capabilities, l’onboarding s’arrête prématurément.
Le pays passé dans country doit être un pays supporté par Stripe pour les comptes connectés ; la liste à jour vit sur cette page. Si un vendeur ne peut pas être enregistré dans un pays supporté, Stripe Connect n’est pas la bonne réponse et il faudra orienter vers un PSP local.
Étape 4 — Générer le lien d’onboarding hébergé
Le compte est créé, mais le vendeur doit remplir son KYC. On génère un Account Link, qui est une URL temporaire vers un formulaire hébergé par Stripe. La plateforme redirige le navigateur du vendeur vers cette URL ; quand il termine le formulaire, Stripe le redirige vers return_url ; en cas d’expiration, vers refresh_url.
app.post('/api/sellers/:id/onboarding-link', async (req, res) => {
const accountId = req.params.id
const link = await stripe.accountLinks.create({
account: accountId,
refresh_url: `${process.env.PLATFORM_URL}/onboarding/refresh`,
return_url: `${process.env.PLATFORM_URL}/onboarding/complete`,
type: 'account_onboarding',
})
res.json({ url: link.url })
})
Le lien généré expire après quelques minutes (durée non garantie côté API, à régénérer à chaque tentative). C’est volontaire : si le vendeur abandonne le formulaire et revient plus tard, on régénère un nouveau lien plutôt que de garder un état persistant. Le client front-end appelle d’abord /api/sellers, stocke accountId, puis appelle /api/sellers/:id/onboarding-link pour obtenir l’URL et faire un window.location.href = url.
Côté UX, on affiche une page de transition côté return_url qui dit « Nous vérifions ton compte, tu seras notifié quand il sera prêt à recevoir des paiements ». L’activation effective du compte n’est pas instantanée : Stripe peut demander des justificatifs supplémentaires (CNI, justificatif de domicile, photo selfie). C’est le webhook account.updated qui notifie la plateforme du basculement.
Étape 5 — Recevoir le webhook account.updated
Le webhook account.updated est tiré chaque fois que Stripe modifie le statut d’un compte connecté : passage en mode actif, blocage, demande de justificatif. La plateforme doit l’écouter pour mettre à jour son propre état (interdire la mise en vente tant que le compte n’est pas charges_enabled, par exemple).
app.post(
'/webhooks/stripe',
express.raw({ type: 'application/json' }),
(req, res) => {
const sig = req.headers['stripe-signature'] as string
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET!,
)
} catch {
return res.status(400).send('Invalid signature')
}
if (event.type === 'account.updated') {
const account = event.data.object as Stripe.Account
const ready = account.charges_enabled && account.payouts_enabled
// Mettre à jour la base : seller.status = ready ? 'active' : 'pending'
}
res.sendStatus(200)
},
)
Trois précisions techniques. Le middleware express.raw est obligatoire sur la route webhook : la vérification HMAC se fait sur les bytes bruts, pas sur le JSON parsé. Si on met express.json() en amont, la signature ne matchera jamais et tous les webhooks seront rejetés. Le secret STRIPE_WEBHOOK_SECRET est différent de la clé API : il est généré quand on configure l’endpoint webhook dans Settings → Developers → Webhooks. Et le statut « prêt » d’un compte est l’intersection de charges_enabled et payouts_enabled : un compte peut accepter des cartes mais ne pas pouvoir recevoir les fonds, par exemple si le RIB n’est pas validé.
Étape 6 — Encaisser une transaction sur un compte connecté
Une fois le vendeur actif, la plateforme peut encaisser pour son compte. On crée un Payment Intent en spécifiant le compte connecté comme bénéficiaire et la commission de plateforme. Le client paie sur la plateforme via Stripe Elements ou Checkout, le solde net est crédité au vendeur, la commission revient à la plateforme.
app.post('/api/orders/:sellerId/checkout', async (req, res) => {
const { sellerId } = req.params
const { amountCents } = req.body // ex: 5000 pour 50 EUR
const platformFee = Math.round(amountCents * 0.10) // 10 % commission
const session = await stripe.checkout.sessions.create({
mode: 'payment',
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: 'eur',
product_data: { name: 'Article du vendeur' },
unit_amount: amountCents,
},
quantity: 1,
},
],
payment_intent_data: {
application_fee_amount: platformFee,
transfer_data: { destination: sellerId },
},
success_url: `${process.env.PLATFORM_URL}/order/success`,
cancel_url: `${process.env.PLATFORM_URL}/order/cancel`,
})
res.json({ checkoutUrl: session.url })
})
Le mécanisme de répartition est entièrement déclaratif. application_fee_amount définit la commission qui va à la plateforme ; transfer_data.destination indique le compte connecté qui reçoit le solde. Stripe se charge de la comptabilisation, des bilans, et des virements automatiques vers le RIB du vendeur (par défaut quotidiens en rolling sur la plupart des pays, configurable en hebdomadaire ou mensuel selon les besoins).
Côté client, on récupère checkoutUrl depuis l’API et on fait window.location.href = checkoutUrl. Le client paie sur la page Stripe hébergée, Stripe redirige vers success_url avec le session_id en query parameter. L’application peut alors interroger l’API pour vérifier le statut et marquer la commande comme payée.
Étape 7 — Tester de bout en bout
Stripe fournit des cartes de test documentées dans cette page. La carte 4242 4242 4242 4242 avec n’importe quelle date future et CVV à trois chiffres simule un paiement réussi sans 3DS. La carte 4000 0027 6000 3184 simule un paiement avec challenge 3DS2 obligatoire. La carte 4000 0000 0000 9995 simule un fonds insuffisant.
Pour tester les webhooks en local sans déployer, on installe Stripe CLI :
npm install -g stripe
stripe login
stripe listen --forward-to localhost:3000/webhooks/stripe
La commande affiche un secret webhook temporaire (whsec_...) qu’on copie dans .env à la place du secret de production. Toute action effectuée sur le compte test (création d’un compte connecté, paiement, refund) est forwardée vers le serveur local. C’est l’outil de référence pour développer sereinement, et il existe en versions Linux, macOS et Windows.
Étape 8 — Vérifier le scénario complet
On déroule un scénario type pour valider que tout fonctionne. Premier appel : curl -X POST http://localhost:3000/api/sellers -H 'Content-Type: application/json' -d '{"email":"vendeur@test.com"}' doit retourner un accountId commençant par acct_. Deuxième appel : POST /api/sellers/acct_xxx/onboarding-link doit retourner une URL HTTPS Stripe ; ouvrir cette URL dans le navigateur affiche le formulaire d’onboarding hébergé.
On remplit le formulaire avec des données fictives mais cohérentes (nom, adresse, RIB de test fourni par Stripe). À la fin, Stripe redirige vers return_url et le terminal qui exécute stripe listen doit afficher un événement account.updated. Troisième étape : appeler /api/orders/acct_xxx/checkout avec un montant, ouvrir la checkoutUrl, payer avec 4242 4242 4242 4242. Le terminal doit afficher checkout.session.completed et payment_intent.succeeded.
Si tous ces signaux arrivent dans l’ordre, l’intégration est fonctionnelle et on peut passer au mode live en remplaçant les clés.
Étape 9 — Préparer le passage en mode live
Le passage en production exige plusieurs précautions. La première est de revérifier les claims de l’entité légale qui détiendra le compte plateforme : raison sociale, identifiant fiscal, RIB de réception des commissions. Stripe vérifie ces informations à l’activation et toute incohérence retarde la mise en service. La page Settings → Activate account du tableau de bord guide ce processus.
La deuxième précaution est de configurer les bons emails de notification. Stripe envoie à l’adresse configurée dans Settings les alertes sur les disputes, les chargebacks, et les demandes de pièce justificative. Une équipe paiement qui rate une demande de pièce voit son compte basculer en mode restreint sous 7 à 14 jours. Mieux vaut une boîte aux lettres partagée surveillée qu’une adresse personnelle d’un ingénieur qui peut être en congé.
La troisième précaution est d’établir une stratégie de gestion des disputes. Une dispute (chargeback) survient quand le client conteste une charge auprès de sa banque. Stripe met automatiquement les fonds en réserve, et la plateforme dispose d’une fenêtre limitée pour soumettre des preuves (facture, échange email, capture d’écran de livraison). Sans réponse, la dispute est perdue d’office. Une procédure interne avec un binôme support / paiement responsable de la collecte des preuves dans les 48 heures réduit drastiquement les pertes.
La quatrième précaution est la gestion des secrets. La clé secrète de production ne doit jamais quitter un coffre dédié (HashiCorp Vault, Doppler, AWS Secrets Manager, GCP Secret Manager). Elle est injectée à l’exécution dans les variables d’environnement, jamais commitée, jamais loggée, jamais exposée dans une URL. Un audit régulier des accès à la clé (qui l’a lue, quand) fait partie de l’hygiène de base.
Étape 10 — Observer la production
Une intégration paiement non observée est un risque permanent. Trois indicateurs minimaux à instrumenter : le taux de succès des charges (cible > 95 % en zone SCA), la latence de réponse de l’API Stripe (95e percentile sous 800 ms en région Europe), et le délai entre événement webhook et son traitement complet (cible sous 5 secondes).
Le SDK Stripe expose un événement request et response qu’on peut capturer pour mesurer la latence côté serveur. On wrappe ensuite ces métriques dans Prometheus ou Datadog. Le tableau de bord Stripe expose les mêmes données dans Reports → Sigma, mais l’avantage d’un dashboard interne est qu’il couvre aussi les erreurs côté application (parsing de webhook, échec d’enregistrement en base) qui n’apparaissent pas chez Stripe.
Côté alerting, on configure deux types d’alertes. Les alertes volume : volume de charges en heure courante < 50 % de la moyenne 7 jours sur le même créneau ; cette alerte attrape les pannes silencieuses où l’API Stripe répond mais l’application ne déclenche plus de charge. Les alertes erreur : taux d’erreur 5xx du SDK > 2 % sur une fenêtre de 5 minutes ; cette alerte attrape les incidents Stripe ou les bugs de configuration.
Erreurs fréquentes
| Erreur | Cause | Solution |
|---|---|---|
account_invalid au paiement |
Le compte n’a pas charges_enabled=true |
Attendre la fin de l’onboarding KYC avant d’autoriser la mise en vente |
| Webhook 400 « No signatures found » | express.json() a parsé le body avant la vérification |
Utiliser express.raw uniquement sur la route webhook |
application_fee_amount rejeté |
La capability transfers manque sur le compte connecté |
L’inclure dans capabilities à la création du compte |
| Onboarding link 404 | Le lien a expiré (quelques minutes après création) | Régénérer un Account Link à chaque tentative |
| Compte bloqué après quelques jours | Documents KYC manquants ou incohérents | Surveiller requirements.currently_due et notifier le vendeur |
Ressources
- Stripe Connect Express — documentation officielle
- Stripe API — Create Account
- Stripe API — Account Links
- Destination charges — application fee et transfer
- Stripe CLI — installation et commandes
- Cartes de test et scénarios
- Article connexe : Webhooks paiement sécurisés : signature et protection contre le rejeu
- Article connexe : Idempotency keys : éviter les doubles paiements