Ambassly
← Tous les articles

Comment fonctionne vraiment le suivi d'affiliation avec Stripe : cookies, webhooks et reprises

Publié le · L'équipe Ambassly

Si vous vendez via Stripe et que vous mettez en place un programme d'affiliation, ou que vous construisez le suivi vous-même, il y a en réalité quatre problèmes à résoudre : faire arriver un code de parrainage jusqu'au visiteur, faire voyager ce code jusqu'à l'objet Stripe, être informé du paiement de façon fiable, et gérer les remboursements sans payer de commission sur de l'argent qui est revenu. Voici comment chaque partie fonctionne réellement.

1. Faire arriver le code jusqu'au visiteur

Un lien d'affiliation ressemble à votresite.com/?via=CODE ou pointe vers une redirection courte comme votresite.com/r/CODE. La version en redirection est préférable car elle n'affiche jamais le code brut dans la barre d'adresse et permet d'enregistrer le clic côté serveur avant de rediriger le visiteur.

Une fois le visiteur arrivé, vous devez vous souvenir de ce code jusqu'à la conversion, qui peut avoir lieu quelques minutes plus tard ou plusieurs semaines plus tard. L'approche standard est un cookie propriétaire, quelque chose comme ambassly_ref, défini pour 60 jours avec SameSite=Lax, accompagné d'un miroir en localStorage comme chemin de lecture de secours.

Voici la partie que la plupart des tutoriels sautent : si vous définissez ce cookie depuis du JavaScript côté client, l'Intelligent Tracking Prevention de Safari limite sa durée de vie à environ sept jours, quelle que soit l'expiration demandée. Un visiteur qui met un tutoriel en favori et revient acheter trois semaines plus tard sur Safari perdra silencieusement son attribution. La solution consiste à définir le cookie via un en-tête de réponse HTTP Set-Cookie depuis votre serveur plutôt que via document.cookie, car le plafond de l'ITP s'applique spécifiquement aux cookies définis côté client, pas à ceux définis côté serveur. Si vous ne pouvez pas le faire pour chaque requête, soyez au minimum honnête avec vos affiliés sur cette limite plutôt que de sous-déclarer discrètement leurs conversions.

2. Faire arriver le code jusqu'à l'objet Stripe

Un cookie ne vous aide que jusqu'au moment de l'achat. Après cela, il faut que le code voyage avec le paiement lui-même, car les cookies ne survivent pas toujours aux redirections vers un checkout hébergé par Stripe et retour, selon la configuration du navigateur.

Stripe vous donne deux endroits où le placer :

  • client_reference_id sur une Checkout Session, un champ de type chaîne unique conçu exactement pour cela : rattacher votre propre référence à un paiement.
  • metadata, un objet clé/valeur libre disponible sur les Checkout Sessions, les PaymentIntents et les Subscriptions.

Utiliser les deux est le schéma le plus sûr. Définissez client_reference_id sur le code d'affiliation, et définissez aussi metadata.ambassly_ref (ou toute autre clé de votre choix) sur la même valeur. client_reference_id est le champ unique le plus idiomatique, mais les métadonnées survivent sur des objets en aval comme les factures d'une façon parfois plus pratique à interroger, donc la ceinture et les bretelles ne coûtent rien ici.

const session = await stripe.checkout.sessions.create({
  client_reference_id: referralCode,
  metadata: { ambassly_ref: referralCode },
  line_items: [...],
  mode: "subscription",
  success_url: "...",
  cancel_url: "...",
});

3. En être informé de façon fiable : webhooks et idempotence

Il faut maintenant savoir quand cette Checkout Session se termine réellement, et quand les factures suivantes sont payées s'il s'agit d'un abonnement. Cela suppose un endpoint webhook qui écoute au minimum :

  • checkout.session.completed, pour résoudre le code et créer l'enregistrement de parrainage initial
  • invoice.paid, pour créer une commission à chaque cycle de facturation dans les programmes récurrents
  • charge.refunded et customer.subscription.deleted, pour déclencher les reprises

Le détail qui compte vraiment ici est l'idempotence. La documentation webhook de Stripe elle-même est explicite : votre endpoint recevra parfois le même événement plus d'une fois, parce que Stripe retente tout ce qui n'est pas une réponse 2xx propre, et des incidents réseau surviennent des deux côtés. Si votre gestionnaire n'est pas idempotent, un événement invoice.paid retenté crée une seconde commission pour la même facture, et vous devez alors deux fois la même somme à un affilié pour un seul paiement.

La solution est simple : conservez une table des IDs d'événements Stripe traités (stripe_event_id, contrainte d'unicité), et insérez-y avant de faire quoi que ce soit d'autre dans le gestionnaire. Si l'insertion échoue sur la contrainte d'unicité, vous avez déjà vu cet événement, donc ignorez-le et renvoyez 200. Chaque effet de bord en aval, créer une commission, mettre à jour le statut d'un parrainage, ne devrait avoir lieu qu'après le succès de cette insertion.

4. Les reprises sur remboursement

Le dernier élément est celui dont chaque programme d'affiliation a finalement besoin et que peu construisent en amont : que se passe-t-il quand le client est remboursé, ou annule et que le paiement est inversé. Si vous avez déjà marqué une commission comme en attente ou approuvée et que la charge sous-jacente est remboursée, cette commission doit passer à un statut repris, pas rester silencieusement dans les livres.

Si la commission a déjà été payée avant que le remboursement n'arrive, vous ne pouvez pas annuler l'envoi d'argent, donc l'approche honnête est un ajustement négatif : une nouvelle entrée de registre qui se compense sur le prochain paiement de l'affilié. Dans les deux cas, cela ne fonctionne que si chaque commission a un historique d'événements, pas juste un statut courant, afin que vous (et votre affilié, s'il le demande un jour) puissiez voir exactement pourquoi une commission a changé.

C'est aussi pourquoi une période de blocage avant paiement compte. Si vous attendez 30 à 45 jours avant qu'une commission soit éligible au paiement, en calquant approximativement votre fenêtre de remboursement, la plupart des remboursements arrivent avant que vous n'ayez payé qui que ce soit, ce qui évite presque entièrement le cas de reprise après paiement.

Le construire versus l'acheter

Rien de tout cela n'est exotique. C'est un cookie, deux champs Stripe, un gestionnaire de webhook idempotent, et une machine à états avec un historique d'audit. De nombreuses équipes le construisent elles-mêmes en un week-end et cela fonctionne bien un moment. Là où cela a tendance à casser, ce sont exactement les points ci-dessus : le plafond de cookie de Safari, le webhook retenté, le remboursement qui arrive après qu'un paiement est déjà parti. Nous avons construit Ambassly précisément pour que ces trois modes d'échec soient traités une fois, correctement, plutôt que chaque équipe d'outil dev les redécouvre indépendamment.

Si vous construisez cela en interne, la documentation Stripe sur Connect et sur la gestion de requêtes idempotentes mérite d'être lue en entier avant de livrer votre premier gestionnaire de webhook. C'est un après-midi bien investi de toute façon.