Avant de commencer
Tout ce que vous devez savoir avant de toucher la moindre ligne de code.
Qui peut devenir partenaire ?
N'importe quel propriétaire d'un SaaS qui fonctionne avec des crédits, des quotas ou des abonnements. Que vous facturies à l'utilisation, au forfait mensuel ou à la consommation — dès que vos utilisateurs ont un « solde » quelque part dans votre app, vous êtes éligible.
Prérequis techniques
- Un serveur capable de recevoir des requêtes HTTP POST. Next.js, Node/Express, FastAPI, Laravel, Bubble avec un plugin API, n8n, Make — n'importe quelle technologie qui peut répondre à une URL publique.
- Un endroit pour stocker une variable secrète. Une variable d'environnement suffit (
.env, Vercel Vars, Railway, Render…). Vous n'avez PAS besoin d'être développeur senior.
Ce que vous y gagnez
Unibund fonctionne sur un système de crédits arrimé à l'euro (1 crédit = 1 centime — voir [L'unité Unibund](#unite)). Vous gagnez sur chaque crédit consommé chez vous par un utilisateur Unibund, selon une grille transparente (voir [Combien vous gagnez](#gains)). Le taux de conversion entre vos crédits et ceux d'Unibund est fixé à l'inscription (voir [Le taux de conversion](#conversion)).
- ~30 minutes pour le mode test (Niveau 1) : un seul endpoint, vérification de signature, idempotence.
- ~1 heure supplémentaire pour la vraie intégration (Niveau 2) : ajout de la liaison de compte par code.
- +10 minutes pour connecter votre compte Stripe (Stripe Connect) et recevoir vos reversements mensuels.
L'unité Unibund
Pour ne dépendre d'aucune réévaluation tarifaire, l'unité Unibund est arrimée à un sous-multiple stable de l'euro. C'est simple, c'est lisible, ça ne bouge pas.
La règle
1 crédit Unibund = 1 centime d'euro (1 ct) 100 crédits = 1 € 1 000 crédits = 10 € 100 000 crédits = 1 000 €
Pourquoi cette règle est précieuse pour vous
- Vos calculs de gains sont des additions d'entiers — jamais de virgule, ni de taux de change à recalculer.
- Pas d'érosion par l'inflation côté Unibund : si l'euro évolue, le crédit évolue avec lui, automatiquement.
- Les utilisateurs raisonnent en crédits comme en monnaie réelle — ils achètent une recharge, ils voient leur solde diminuer, ils comprennent instantanément ce qu'ils paient et ce qu'ils consomment.
Combien vous gagnez
Unibund vend vos crédits à un tarif réduit par rapport à votre prix public direct. C'est ce qui attire les utilisateurs. La marge d'Unibund est assumée et transparente.
Pour 1 crédit consommé chez vous
Exemple à votre prix public de 0,10 € (10 crédits Unibund)
Le même modèle, à chaque échelle
Vos gains mensuels selon votre nombre d'utilisateurs actifs
SaaS qui démarre
50 utilisateurs actifs
200 000
crédits (2 000 €) / mois
25 000 crédits consommés/mois
SaaS qui décolle
500 utilisateurs actifs
2 000 000
crédits (20 000 €) / mois
250 000 crédits consommés/mois
SaaS établi
5 000 utilisateurs actifs
20 000 000
crédits (200 000 €) / mois
2 500 000 crédits consommés/mois
Hypothèses : ~500 crédits consommés / utilisateur actif / mois, grille de reversement à −20 %. Unibund ne garantit ni volume d'utilisateurs ni part de marché — ces paliers illustrent l'effet de levier, pas une promesse de revenu.
Quand et comment on vous paye
- Reversement mensuel — Déclenché début du mois suivant. Tous les crédits consommés du 1er au dernier jour du mois M sont reversés début M+1.
- Via Stripe Connect — Vous connectez votre compte Stripe à l'inscription. Les fonds arrivent sur votre compte bancaire selon votre cycle Stripe habituel.
- Suivi détaillé — Des reversements dans votre tableau de bord partenaire.
- Sur la roadmap — Reversements plus fréquents (hebdomadaires) — pas garantis à date, à l'étude pour 2026.
Le taux de conversion
Vos crédits n'ont pas la même « valeur unitaire » que les crédits Unibund. Le taux de conversion dit combien de crédits Unibund valent 1 crédit chez vous.
Vous fixez le taux à l'inscription
- Vous déclarez votre prix public direct par crédit chez vous (ex. 0,10 €, soit 10 crédits Unibund).
- Ce prix EST le taux de conversion : combien de crédits Unibund l'utilisateur doit dépenser pour obtenir 1 crédit chez vous.
- Le taux est figé pour la durée du partenariat. Vous pouvez le renégocier depuis votre tableau de bord (préavis 30 jours pour ne pas casser les paniers d'achat en cours).
Important : Unibund fait la conversion pour vous
Quand Unibund vous envoie un grant, le champ credits du payload est DÉJÀ dans VOTRE unité (= des crédits chez vous, conversion appliquée).
Vous créditez ce nombre TEL QUEL côté votre SaaS. Vous ne refaites JAMAIS le calcul de taux — sinon vous comptez le taux deux fois et vous créditez au mauvais montant.
C'est pour ça que le taux que vous déclarez à l'inscription doit être le bon : il pilote tous vos futurs grants.
Exemple complet, bout en bout
Votre prix public : 0,10 € / crédit chez vous (= 10 crédits Unibund par crédit chez vous). Un utilisateur Unibund veut 500 crédits chez vous : il dépense 500 × 9 = 4 500 crédits Unibund (= 45 €) chez Unibund. Unibund convertit en interne et envoie un grant à votre endpoint avec credits = 500 (c'est l'unité chez vous, déjà convertie). Vous ajoutez 500 crédits à son compte chez vous. Point. Côté reversement : Unibund vous reversera 500 × 8 = 4 000 crédits Unibund (= 40 €) au prochain payout mensuel. Marge Unibund sur cette opération : 500 crédits (= 5 €).
Démarrer en 4 étapes
Le chemin le plus court pour devenir partenaire Unibund, de zéro à la production.
- 1Inscrivez-vous comme partenaire
Créez votre compte partenaire sur l'espace partenaire. Unibund vous fournit votre secret partagé (
UNIBUND_SECRET) à cette étape. Cette étape inclut la connexion de votre compte Stripe via Stripe Connect pour recevoir vos reversements mensuels, et la déclaration de votre taux de conversion (voir [#conversion](#conversion)). - 2Récupérez votre secret partagé
Depuis votre tableau de bord partenaire (disponible après inscription), copiez votre
UNIBUND_SECRETet stockez-le en variable d'environnement côté votre SaaS. Ne le commitez jamais. - 3Mode test — faites fonctionner /credits (Niveau 1)
Implémentez l'endpoint
POST /api/unibund/credits. C'est une étape de test uniquement pour valider que votre logique de crédit et de signature fonctionne. Vous n'êtes pas encore partenaire en production. - 4Vrai partenariat — ajoutez la liaison de compte (Niveau 2)
Ajoutez
POST /api/unibund/link/startetPOST /api/unibund/link/verify. C'est cette étape qui vous donne le statut de partenaire Unibund et débloque les échanges réels en production.
Si vous utilisez un outil no-code ou une IA
Vous n'écrivez pas le code vous-même ? Collez l'un de ces prompts dans votre assistant (v0, Cursor, Lovable…). Ils reprennent fidèlement les en-têtes et le payload à 5 champs.
Intègre l'API partenaire Unibund (endpoint de crédit). Crée UN endpoint
HTTP POST /api/unibund/credits qui :
1. Lit le corps JSON BRUT (raw body) AVANT tout parsing.
2. Vérifie l'en-tête "Unibund-Signature" au format "t=<unix>,v1=<hex>" :
v1 doit égaler HMAC_SHA256(secret, `${t}.${rawBody}`) en hexadécimal,
comparé en constant-time. Le secret vient de la variable d'env {SECRET_PARTAGE}.
Rejette 401 si invalide ou si t date de plus de 300 secondes (anti-replay).
3. IDEMPOTENCE : lit l'en-tête "Unibund-Event-Id". Si déjà traité, renvoie le
résultat précédent SANS recréditer (replayed: true).
4. Crédite l'utilisateur :
- si body.user.external_id est présent → crédite CE compte exact ;
- sinon → trouve-ou-crée le compte par body.user.email.
Ajoute body.credits au solde TEL QUEL (déjà converti, aucun calcul de taux).
5. Réponds 200 { "ok": true, "external_user_id": "...", "new_balance": <n> }
UNIQUEMENT si le crédit a réussi. Sinon réponds 500 (Unibund réessaiera).
Corps reçu (5 champs) :
{ "event":"credit.grant", "event_id":"evt_abc123", "created":1717761600,
"user":{ "external_id":"user_8842", "email":"{EMAIL_CLIENT}", "name":"Marie D." },
"credits":{MONTANT} }
Utilise mon framework et ma base de données. Donne-moi le code complet de la route.Ajoute la liaison de compte Unibund par code à usage unique. Deux endpoints :
A) POST /api/unibund/link/start — appelé par MON PROPRE frontend (bouton
"Connecter à Unibund"), authentifié par MA session utilisateur.
- génère un code court lisible (ex. "ULINK-7Q2F"), durée de vie 10 minutes ;
- stocke { code, mon_user_id_interne, expires_at } ;
- renvoie { "code": "ULINK-7Q2F", "expires_in": 600 }. Mon frontend l'affiche.
B) POST /api/unibund/link/verify — appelé par UNIBUND, signé exactement comme
l'endpoint de crédit (en-tête Unibund-Signature "t=,v1=", HMAC sur
`${t}.${rawBody}`, secret {SECRET_PARTAGE}, anti-replay 300s).
- corps : { "code": "ULINK-7Q2F" }
- si le code existe, n'est pas expiré et pas déjà consommé : marque-le
consommé, renvoie 200 { "ok": true, "external_user_id": <mon_user_id>,
"email": <optionnel> } ;
- sinon : 404 { "ok": false, "error": "invalid_code" }.
Réutilise ma fonction de vérification de signature. Donne-moi le code complet.# secret partagé = {SECRET_PARTAGE} (depuis votre dashboard partenaire)
# t = horodatage Unix de l'appel
# corps = le corps JSON brut reçu
#
# v1 = HMAC_SHA256( {SECRET_PARTAGE} , "<t>.<corps_brut>" ) → hex
# en-tête à comparer : Unibund-Signature: t=<t>,v1=<v1>C'est quoi connecter mon SaaS ?
Vos clients possèdent des crédits sur Unibund. Quand l'un d'eux choisit d'échanger ses crédits chez vous, Unibund prévient votre SaaS. Votre seul travail : retrouver le bon utilisateur et lui ajouter ces crédits.
Concrètement, vous exposez une ou deux adresses web (des endpoints). Unibund les appelle automatiquement, vous créditez l'utilisateur, vous répondez 200. C'est tout — pas de facture à émettre, pas d'abonnement à gérer.
Niveau 1 n'est pas un point de départ valable en production
Niveau 1 est un bac à sable : il sert uniquement à valider que votre endpoint /credits reçoit bien les appels et crédite correctement. Il ne suffit PAS pour devenir partenaire Unibund. Niveau 2 — la liaison de compte — est requis pour les échanges réels. Vous passez par Niveau 1 le temps de tester, puis vous montez en Niveau 2.
Niveau 1 — recevoir des crédits
Bac à sable — mode test uniquement
Cette configuration ne suffit pas pour devenir partenaire Unibund : elle sert à valider votre endpoint /credits en isolation. Pour les échanges réels et le statut partenaire, passez au Niveau 2.
Vous créez UN seul point d'entrée : POST /api/unibund/credits. En clair, Unibund vous dit « ajoute X crédits à tel utilisateur ». Vous trouvez (ou créez) le compte concerné, et vous créditez.
Le montant est déjà converti
Le nombre dans le champ credits est DÉJÀ exprimé dans VOTRE monnaie de crédits. La conversion au taux négocié est faite côté Unibund. Vous créditez ce nombre TEL QUEL — vous ne refaites aucun calcul de taux.
Les 3 règles à ne pas rater
- Anti-doublon (idempotence). Chaque envoi porte l'en-tête
Unibund-Event-Id. Si vous recevez DEUX fois le même id, vous ne créditez QU'UNE fois : stockez les id déjà traités et renvoyez le même résultat avecreplayed: true. Deux appels identiques = un seul crédit. - Répondez 200 SEULEMENT une fois les crédits réellement ajoutés. Trop tôt, et Unibund croit que c'est fait. En cas d'erreur, renvoyez
500: Unibund réessaiera (jusqu'à 3 fois, attentes 2s / 8s / 32s), puis ANNULERA de son côté. L'utilisateur est alors recrédité chez Unibund — un échec propre ne fait donc perdre de crédits à personne. - Vérifiez la signature AVANT de créditer (voir la section Sécurité).
Ce qu'Unibund vous envoie
{
"event": "credit.grant",
"event_id": "evt_abc123",
"created": 1717761600,
"user": {
"external_id": "user_8842",
"email": "marie@example.com",
"name": "Marie D."
},
"credits": 500
}external_id est rempli dès que le compte a été lié (Niveau 2). S'il est null (Niveau 1 ou pas encore lié), retrouvez le compte par email.
{ "ok": true, "external_user_id": "user_8842", "new_balance": 1500 }{ "ok": true, "external_user_id": "user_8842", "new_balance": 1500, "replayed": true }Niveau 2 — relier les comptes proprement
Vrai partenariat — la configuration qui compte
C'est la configuration qui vous donne le badge "partenaire d'Unibund" et permet les échanges en production. Sans cette étape, vous restez en mode test.
Créditer uniquement par email casse si le client a un autre email chez vous, ou s'il s'est inscrit via Google ou par téléphone — vous risquez alors de créer un compte en double. La liaison règle ça : elle vous donne un identifiant stable, external_id.
L'idée : l'utilisateur, connecté à SON compte chez vous, génère un code à usage unique et le colle dans Unibund. Ce code prouve qu'il possède bien le compte. Vous ajoutez deux points d'entrée :
POST /api/unibund/link/start** — appelé par VOTRE propre bouton « Connecter à Unibund », sur VOTRE site, avec votre session utilisateur habituelle. Vous générez un code à usage unique (durée de vie ~10 min) pour l'utilisateur connecté, et vous l'affichez. ⚠️ Cet endpoint n'est PAS signé par Unibund — c'est votre frontend qui l'appelle.POST /api/unibund/link/verify** — appelé par Unibund (signé) quand le client colle son code chez nous. Vous vérifiez le code, le marquez consommé, et vous renvoyez votreexternal_user_idstable.
{ "code": "ULINK-7Q2F", "expires_in": 600 }// Unibund vous envoie :
{ "code": "ULINK-7Q2F" }
// Vous répondez (200) :
{ "ok": true, "external_user_id": "user_8842", "email": "marie@example.com" }
// Code invalide, expiré ou déjà utilisé (404) :
{ "ok": false, "error": "invalid_code" }Ensuite, tout devient fiable
Une fois la liaison faite, les octrois sur /api/unibund/credits portent cet external_id. Vous créditez ce compte précis, et l'email ne sert plus que de secours.
Sécurité — la signature
Chaque appel VENANT d'Unibund porte l'en-tête Unibund-Signature au format t=<horodatage>,v1=<hash>. Le v1 est un HMAC-SHA256 du secret partagé, calculé sur la chaîne ${t}.${corps_brut}. Vous recalculez ce hash de votre côté, vous comparez, et vous rejetez (401) si ça ne correspond pas OU si t a plus de 300 secondes (5 minutes).
Où s'applique la signature ?
Elle s'applique à /api/unibund/credits et à /api/unibund/link/verify. PAS à /api/unibund/link/start, qui est appelé par votre propre frontend authentifié. Le secret partagé vient de votre tableau de bord partenaire.
attendu = HMAC_SHA256( secret , t + "." + corps_brut ) → en hexadécimal
valide = (attendu === v1) ET (maintenant − t < 300 s)import crypto from "crypto"
const SECRET = process.env.UNIBUND_SECRET!
// Réutilisable pour /credits ET /link/verify
function verify(raw: string, header: string): boolean {
const p = Object.fromEntries(header.split(",").map((x) => x.split("=")))
const expected = crypto.createHmac("sha256", SECRET).update(`${p.t}.${raw}`).digest("hex")
return (
!!p.v1 &&
crypto.timingSafeEqual(Buffer.from(p.v1), Buffer.from(expected)) &&
Math.abs(Date.now() / 1000 - Number(p.t)) < 300
)
}export async function POST(req: Request) {
const raw = await req.text()
if (!verify(raw, req.headers.get("unibund-signature") ?? ""))
return new Response("invalid signature", { status: 401 })
// Idempotence : un même Unibund-Event-Id ne crédite qu'une fois.
const eventId = req.headers.get("unibund-event-id") ?? ""
if (await alreadyProcessed(eventId)) return Response.json(await getResult(eventId))
const body = JSON.parse(raw)
const user = body.user.external_id
? await getUserByExternalId(body.user.external_id) // compte lié → exact
: await findOrCreateUser(body.user.email, body.user.name) // sinon, par email
const newBalance = await addCredits(user.id, body.credits)
const result = { ok: true, external_user_id: user.id, new_balance: newBalance }
await markProcessed(eventId, result)
return Response.json(result)
}Tester que ça marche
- 1Envoyez un crédit de test depuis votre tableau de bord partenaire (« Envoyer un grant de test »). Unibund envoie un grant signé à votre endpoint /api/unibund/credits.
- 2Vérifiez le solde : l'utilisateur de test doit avoir reçu le montant du champ
credits. - 3Rejouez le même grant avec le même
Unibund-Event-Id: le solde ne doit PAS bouger, et vous renvoyezreplayed: true. C'est la preuve que votre anti-doublon fonctionne.
curl -X POST {URL_DE_BASE}/api/unibund/credits \
-H "Content-Type: application/json" \
-H "Unibund-Signature: t=1717761600,v1=<hmac_du_dashboard>" \
-H "Unibund-Event-Id: evt_test_001" \
-d '{"event":"credit.grant","event_id":"evt_test_001","created":1717761600,
"user":{"external_id":null,"email":"{EMAIL_CLIENT}","name":"Test"},
"credits":{MONTANT}}'| Votre réponse | Ce qu'Unibund fait |
|---|---|
| 200 | Grant confirmé, terminé. |
| 401 | Signature invalide → alerte dashboard, aucun nouvel essai. |
| 404 invalid_code (liaison) | L'utilisateur est invité à régénérer un code. |
| 500 / timeout | Nouvel essai : 3 fois, attentes 2s → 8s → 32s. |
| échec après 3 essais | Grant marqué échoué, l'utilisateur est remboursé côté Unibund. |
La règle d'or
Répondez 200 uniquement si les crédits ont vraiment été ajoutés.