@kundai/nestjs
v1.1.0
Published
Lib NestJS embarquable pour intégrer le tracking KundAi en 2 lignes : module forRoot(), contrôleurs /tracking/* et service injectable typé.
Downloads
380
Maintainers
Readme
@kundai/nestjs
Module NestJS officiel pour intégrer le tracking KundAi dans une application backend NestJS.
Sommaire
- Installation
- Configuration
- Conversions webhook
- Événements serveur
- Autres méthodes
- Routes exposées automatiquement
- Référence API
- Flux complet frontend + backend
Installation
npm install @kundai/nestjsVersions NestJS supportées : NestJS 10+
Configuration
// app.module.ts
import { Module } from '@nestjs/common';
import { NestjsModule } from '@kundai/nestjs';
@Module({
imports: [
NestjsModule.forRoot({
baseUrl: process.env.KUNDAI_TRACKING_URL,
apiKey: process.env.KUNDAI_API_KEY,
appId: 'mon-app-api',
webhookSecret: process.env.KUNDAI_WEBHOOK_SECRET,
}),
],
})
export class AppModule {}Options de configuration
| Option | Type | Requis | Description |
|---|---|---|---|
| baseUrl | string | ✅ | URL du endpoint tracking KundAi |
| apiKey | string | ✅ | Clé API générée dans le dashboard KundAi |
| appId | string | — | Identifiant de ton app (défaut : "api") |
| webhookSecret | string | — | Secret HMAC pour signer les conversions (défaut : "kundai-dev-secret") |
| timeoutMs | number | — | Timeout des requêtes HTTP en ms (défaut : 5000) |
Variables d'environnement
KUNDAI_TRACKING_URL=https://api.kundai.io/v1/tracking
KUNDAI_API_KEY=kundai_xxxxxxxxxxxx
KUNDAI_WEBHOOK_SECRET=un-secret-fort-en-production
KUNDAI_WEBHOOK_SECRETdoit correspondre àWEBHOOK_SECRETdans le microservice tracking KundAi. En développement, la valeur par défautkundai-dev-secretest utilisée.
Conversions webhook
C'est le cas d'usage principal du module backend : notifier KundAi d'un paiement confirmé par webhook (Flutterwave, Stripe, Orange Money, MTN MoMo, etc.) en le liant à la session navigateur de l'utilisateur.
// payment.service.ts
import { Injectable } from '@nestjs/common';
import { NestjsService } from '@kundai/nestjs';
@Injectable()
export class PaymentService {
constructor(private readonly kundai: NestjsService) {}
// Appelé lors du checkout — stocker le sessionId pour le retrouver au webhook
async initiateCheckout(dto: {
userId: string;
planId: string;
amount: number;
currency: string;
sessionId: string; // reçu du frontend Angular via KundaiService.getSessionId()
}) {
const checkoutId = await this.flutterwave.initiate(dto);
await this.db.saveCheckout({ ...dto, checkoutId });
return checkoutId;
}
// Appelé par le webhook Flutterwave/Stripe
async handleWebhook(payload: WebhookPayload) {
const checkout = await this.db.findCheckout(payload.txRef);
await this.kundai.trackConversion({
event: 'purchase',
userId: checkout.userId,
sessionId: checkout.sessionId, // ← lie la conversion à la session navigateur
amount: payload.amount,
currency: payload.currency,
planId: checkout.planId,
metadata: {
txRef: payload.txRef,
provider: 'flutterwave',
},
});
}
}// payment.controller.ts
import { Body, Controller, HttpCode, Post } from '@nestjs/common';
@Controller('payments')
export class PaymentController {
constructor(private readonly paymentService: PaymentService) {}
@Post('checkout')
checkout(
@Body() dto: { planId: string; amount: number; currency: string; sessionId: string },
@CurrentUser() user: AuthUser,
) {
return this.paymentService.initiateCheckout({ ...dto, userId: user.id });
}
@Post('webhook/flutterwave')
@HttpCode(200)
async flutterwaveWebhook(@Body() payload: WebhookPayload) {
await this.paymentService.handleWebhook(payload);
return { status: 'ok' };
}
}Événements serveur
Pour les événements qui se produisent côté serveur et que le frontend ne peut pas observer directement.
// user.service.ts
@Injectable()
export class UserService {
constructor(private readonly kundai: NestjsService) {}
async createAccount(dto: CreateUserDto, sessionId?: string) {
const user = await this.db.createUser(dto);
await this.kundai.trackEvent({
event: 'account_created',
userId: user.id,
sessionId,
properties: {
plan: dto.plan ?? 'free',
country: dto.country,
method: dto.registrationMethod,
},
});
return user;
}
async upgradeSubscription(userId: string, newPlan: string) {
await this.kundai.trackEvent({
event: 'subscription_upgraded',
userId,
properties: { plan: newPlan },
});
}
async deleteAccount(userId: string) {
await this.kundai.trackEvent({
event: 'account_deleted',
userId,
});
}
}Événements recommandés côté serveur
Cycle de vie du compte
| Événement | Propriétés suggérées | Description |
|---|---|---|
| account_created | plan, country, method | Compte créé |
| account_deleted | — | Compte supprimé |
| subscription_upgraded | plan, previous_plan | Montée en plan |
| subscription_downgraded | plan, previous_plan | Descente en plan |
| subscription_cancelled | plan, reason | Résiliation |
| subscription_renewed | plan, amount, currency | Renouvellement |
| trial_started | plan, trial_days | Début d'essai |
| trial_ended | plan, converted | Fin d'essai |
Conversions (déclenchent l'attribution dans KundAi)
| Événement | Propriétés requises | Propriétés optionnelles |
|---|---|---|
| purchase | amount, currency | planId, txRef, provider |
| payment | amount, currency | planId, provider |
| conversion | amount, currency | planId |
| checkout | amount, currency | planId |
Les événements
purchase,payment,conversionetcheckoutdéclenchent le moteur d'attribution KundAi et marquent la session comme convertie. UtilisertrackConversion()plutôt quetrackEvent()pour ces cas — la signature HMAC est gérée automatiquement.
Activité utilisateur
| Événement | Propriétés suggérées | Description |
|---|---|---|
| login | method | Connexion |
| logout | — | Déconnexion |
| password_changed | — | Mot de passe modifié |
| mfa_enabled | — | 2FA activée |
| api_key_created | scope | Clé API créée |
| export_requested | format, entity | Export de données |
Autres méthodes
// Identifier un utilisateur après login (lie userId à la session)
await this.kundai.identify(sessionId, { userId: user.id, email: user.email });
// Démarrer une session côté serveur (SSR, app mobile, CLI)
const session = await this.kundai.startSession({
visitorId: 'visitor_uuid',
entryPageUrl: 'https://mon-app.com',
appId: 'mon-app-mobile',
});
// Enregistrer une vue de page (SSR)
await this.kundai.pageView(session.sessionId, {
visitorId: session.visitorId,
url: 'https://mon-app.com/pricing',
title: 'Tarifs',
});
// Clôturer une session
await this.kundai.endSession(sessionId, 'https://mon-app.com/merci');Routes exposées automatiquement
Quand NestjsModule.forRoot() est importé, ces routes sont disponibles dans ton app.
Elles sont utiles si tu veux que le frontend passe par ton backend avant d'atteindre KundAi.
| Route | Body | Description |
|---|---|---|
| POST /tracking/session | StartSessionRequest | Démarre une session |
| POST /tracking/session/page-view | PageViewRequest + sessionId | Vue de page |
| POST /tracking/session/identify | IdentifyRequest + sessionId | Lie un utilisateur |
| POST /tracking/session/end | { sessionId, exitPageUrl? } | Clôture une session |
| POST /tracking/event | TrackEventRequest | Événement comportemental |
| POST /tracking/conversion | ServerConversionDto | Conversion serveur |
Dans la majorité des cas, le frontend Angular appelle KundAi directement via
@kundai/angular. Ces routes ne sont utiles que si tu veux centraliser tous les appels tracking par ton backend (ex. proxy, enrichissement, validation métier).
Référence API
// Injecter le service
constructor(private readonly kundai: NestjsService) {}
// Notifier une conversion confirmée côté serveur
await kundai.trackConversion(dto: {
event: 'purchase' | 'payment' | 'conversion' | 'checkout' | string
userId: string // requis
sessionId?: string // recommandé — lie la conversion à la session navigateur
visitorId?: string
amount?: number
currency?: string // ISO 4217 : 'XOF', 'EUR', 'USD', 'GHS', 'NGN'...
planId?: string
metadata?: Record<string, unknown>
}): Promise<{ received: boolean; eventId: string }>
// Envoyer un événement comportemental
await kundai.trackEvent(dto: {
event: string
userId?: string
sessionId?: string
visitorId?: string
properties?: Record<string, unknown>
pageUrl?: string
timestamp?: string // ISO 8601, serveur si absent
}): Promise<{ eventId: string }>
// Identifier un utilisateur
await kundai.identify(sessionId: string, dto: {
userId: string
email?: string // haché SHA-256 côté serveur
}): Promise<{ ok: boolean }>
// Démarrer une session
await kundai.startSession(dto: {
visitorId: string
entryPageUrl?: string
clickId?: string
appId?: string
}): Promise<{ sessionId: string; visitorId: string; resumed: boolean }>
// Vue de page
await kundai.pageView(sessionId: string, dto: {
visitorId: string
url: string
title?: string
referrerUrl?: string
viewportWidth?: number
viewportHeight?: number
}): Promise<{ ok: boolean }>
// Clôturer une session
await kundai.endSession(sessionId: string, exitPageUrl?: string): Promise<{ ok: boolean }>Flux complet frontend + backend
1. Utilisateur arrive sur le site avec ?utm_source=facebook&utm_campaign=promo
└─ @kundai/angular capture les UTM, démarre la session → sessionId: "sess_abc"
2. Utilisateur clique "Essayer Pro"
└─ kundai.track('plan_selected', { plan: 'pro' })
3. Utilisateur clique "Payer"
└─ kundai.track('checkout_started', { plan: 'pro', amount: 9900, currency: 'XOF' })
└─ POST /api/payments/checkout { planId: 'pro', sessionId: 'sess_abc' }
4. Backend initie le paiement Flutterwave, stocke { sessionId: 'sess_abc', checkoutId }
5. Flutterwave confirme le paiement par webhook
└─ kundai.trackConversion({ event: 'purchase', userId, sessionId: 'sess_abc', amount: 9900, currency: 'XOF' })
6. KundAi reçoit la conversion liée à la session "sess_abc"
└─ Attribution : conversion attribuée à la campagne Facebook ✅
└─ Dashboard : +1 conversion, +9 900 XOF de revenu attribué à facebook/promo ✅Sans le sessionId à l'étape 5, la conversion arrive dans KundAi sans lien avec
la session → l'attribution est impossible (on sait qu'il y a eu une conversion
mais pas depuis quelle campagne).
