# SDK serveur Node.js

URL: https://traqio.app/fr/docs/sdks/node
Last updated: 2026-10-16

Signalez inscriptions et achats depuis un backend Node.js avec @traqio/node, et rattachez chacun à son clic publicitaire. Express, Next.js, NestJS, Fastify.

`@traqio/node` est le SDK Traqio pour les backends Node.js — la moitié serveur du SDK web de Traqio. La conversion qui compte sur un site — une inscription, un abonnement, un achat — a lieu sur votre backend, là où l’id utilisateur est connu. Ce paquet la signale avec le **trackID**, le click id de la publicité que [@traqio/web](https://traqio.app/fr/docs/sdks/web) a gardé en stockage first-party depuis la page d’arrivée, et Traqio crédite la campagne qui a amené l’utilisateur — [de façon déterministe](https://traqio.app/fr/docs/concepts/web-attribution), par l’id de ce clic, jamais par une estimation. Aucune dépendance ; Express, Next.js, NestJS, Fastify ou `node:http` seul.

## Prérequis

- Node.js 18 ou plus (le SDK utilise le `fetch` natif). ESM et CommonJS, avec les types TypeScript.
- Une **clé serveur** pour le produit (`tq_sk_…`). Ni la clé SDK de l’app ni une clé web ne fonctionnent ici.
- [@traqio/web](https://traqio.app/fr/docs/sdks/web) sur le site, et des publicités qui pointent vers des [liens de tracking](https://traqio.app/fr/docs/concepts/tracking-links) avec une destination site web — c’est ainsi que le trackID arrive jusqu’à votre formulaire d’inscription.

## Mise en place

### Créer une clé serveur

Dans la console, ouvrez **Intégrations → SDK** et créez une **clé serveur**. Elle n’est affichée qu’une fois : placez-la dans l’environnement du backend, jamais dans le code ni dans une page. Traqio n’en garde qu’une empreinte ; une clé perdue est remplacée par une nouvelle, et l’ancienne révoquée.

.env:
```bash
TRAQIO_SECRET_KEY=tq_sk_...
```

### Installer le paquet

```bash
npm install @traqio/node
```

### Créer le client

```ts
import { Traqio } from "@traqio/node";

// One client per process. The key comes from the server's environment, never from code.
export const traqio = new Traqio({ secretKey: process.env.TRAQIO_SECRET_KEY! });
```

Le client ne garde aucune connexion ouverte : un par processus suffit. Il refuse une clé web (`tq_web_…`) et refuse de démarrer dans un navigateur.

### Signaler l’inscription avec son trackID

Appelez `signup()` une fois, là où le compte est créé. @traqio/web écrit le trackID dans le cookie `traq_click` sur votre propre domaine : le navigateur l’envoie donc avec la requête d’inscription.

Express:
```ts
import { trackIdFromRequest } from "@traqio/node";
import { traqio } from "./traqio";

app.post("/signup", async (req, res) => {
  const user = await users.create(req.body);

  // The trackID arrives with the request: @traqio/web set the traq_click cookie.
  const result = await traqio
    .signup({ userId: user.id, trackId: trackIdFromRequest(req) })
    .catch((err) => logger.warn(err)); // never fail the sign-up itself

  res.status(201).json(user);
});
```

Next.js:
```ts
// app/api/signup/route.ts (Next.js App Router)
import { cookies } from "next/headers";
import { TRACK_ID_COOKIE } from "@traqio/node";
import { traqio } from "@/lib/traqio";

export async function POST(request: Request) {
  const user = await users.create(await request.json());
  const trackId = (await cookies()).get(TRACK_ID_COOKIE)?.value ?? null;
  await traqio.signup({ userId: user.id, trackId }).catch(console.warn);
  return Response.json(user, { status: 201 });
}
```

NestJS:
```ts
// users.service.ts (NestJS)
import { Injectable, Logger } from "@nestjs/common";
import { Traqio } from "@traqio/node";

@Injectable()
export class UsersService {
  private readonly traqio = new Traqio({ secretKey: process.env.TRAQIO_SECRET_KEY! });
  private readonly log = new Logger(UsersService.name);

  async register(dto: RegisterDto, trackId: string | null): Promise<User> {
    const user = await this.users.create(dto);
    await this.traqio.signup({ userId: user.id, trackId }).catch((e: Error) => this.log.warn(e.message));
    return user;
  }
}
```

Si le backend est sur un autre domaine que le site, le cookie ne lui parvient pas : envoyez plutôt `Traqio.getTrackId()` depuis le navigateur avec le formulaire. L’inscription est le moment où l’utilisateur est **acquis** ; le dernier clic qui la précède, dans votre [fenêtre d’attribution](https://traqio.app/fr/docs/concepts/attribution-windows), reçoit le crédit.

### Suivre les conversions

```ts
await traqio.track({
  userId: user.id,
  name: "purchase",
  value: 49,
  currency: "EUR",
  transactionId: paymentIntent.id, // one payment, one row — whoever reports it
  store: "stripe",
});
```

Les conversions suivent l’utilisateur : elles sont créditées à la campagne qui l’a acquis. Plusieurs événements peuvent partir en un seul appel :

```ts
await traqio.send({
  userId: user.id,
  events: [
    { name: "trial_started" },
    { name: "purchase", value: 49, currency: "EUR", transactionId: invoice.id, store: "stripe" },
  ],
});
```

## Ce que dit la réponse

```ts
const result = await traqio.signup({ userId: user.id, trackId });

result.trackId;     // "matched" | "unknown" | "expired" | "claimed" | "refused" (absent without a trackID)
result.acquired;    // true: the sign-up is recorded
result.attribution; // { type: "deterministic", network: "meta", campaignId: "…", adsetId, adId }
```

| trackId | Signification |
| --- | --- |
| matched | Le clic appartient désormais à cet utilisateur. Une nouvelle tentative répond de nouveau matched. |
| unknown | Aucun clic de ce produit n’a cet id — une faute de frappe, le lien d’un autre produit, ou une valeur que @traqio/web n’a pas écrite. |
| expired | Le clic est plus ancien que la fenêtre d’attribution du produit. |
| claimed | Un autre utilisateur, ou une installation d’app, possède déjà ce clic. Un clic crédite une seule identité. |
| refused | Le clic a échoué à une règle anti-fraude (il est passé par un réseau d’anonymisation). Il est compté, jamais crédité. |

## Ce qu’il fait aujourd’hui, et ce qu’il ne fait pas

|  | Aujourd’hui |
| --- | --- |
| Rattacher une inscription à son clic, et dire qui a été crédité | Oui — dans la réponse |
| Enregistrer les achats avec valeur, devise et id de paiement, dédoublonnés | Oui |
| Laisser intacts les installations, revenus et ROAS de vos apps | Oui — jamais rattachés à une installation d’app |
| Afficher les conversions web dans les rapports de la console | Oui — par campagne, à côté des installations |
| Envoyer les conversions web à Meta ou TikTok | Pas encore |

Les conversions envoyées ici apparaissent dans **Événements en direct**, marquées comme non rapprochées d’une installation — c’est normal pour un site web. Une inscription créditée à une campagne est comptée dans le rapport ROAS comme une conversion web de cette campagne, à côté de ses installations, et la réponse de chaque appel vous donne l’attribution tout de suite.

## Erreurs et nouvelles tentatives

Une erreur réseau, un timeout, un `429` ou un `5xx` est retenté — deux fois par défaut, avec le même corps, donc rien n’est compté deux fois. Un `4xx` (clé erronée ou révoquée, payload invalide) n’est pas retenté. Les deux se terminent par une `TraqioError` :

```ts
import { TraqioError } from "@traqio/node";

try {
  await traqio.signup({ userId: user.id, trackId });
} catch (err) {
  if (err instanceof TraqioError) {
    // err.status: 401 = wrong or revoked key, 400 = invalid payload, undefined = network
    logger.warn({ status: err.status, body: err.body }, err.message);
  }
}
```

> **Signalez après votre propre écriture** Appelez Traqio une fois le compte ou le paiement enregistré, et ne laissez jamais une erreur Traqio le faire échouer.

## Référence de l’API

TraqioOptions:
```ts
interface TraqioOptions {
  secretKey: string;     // the server key, tq_sk_… — required
  endpoint?: string;     // default "https://api.traqio.app"
  timeoutMs?: number;    // per attempt — default 10000
  maxRetries?: number;   // after a network error, a 429 or a 5xx — default 2
  fetch?: typeof fetch;  // a custom fetch (proxies, tests)
}
```

| Méthode | Ce qu’elle fait |
| --- | --- |
| signup({ userId, trackId?, events? }) | Signale l’inscription — l’acquisition — avec son trackID. La première est conservée. |
| track({ userId, name, value?, currency?, transactionId?, store?, eventId?, trackId? }) | Enregistre une conversion d’un utilisateur connu. |
| send({ userId, trackId?, signup?, events? }) | L’appel brut derrière les deux, pour plusieurs événements à la fois. |
| trackIdFromRequest(req) | Le trackID tiré des cookies d’une requête (Express, Fastify, Koa, node:http, une Request Fetch), ou null. |
| trackIdFromCookieHeader(header) | La même chose, depuis un en-tête Cookie brut. |

> **Niveau de preuve** Testé unitairement, et exécuté de bout en bout — builds ESM et CommonJS — contre une API Traqio en fonctionnement : une inscription portant un trackID est revenue matched avec sa campagne, un achat signalé deux fois a été stocké une fois, une clé erronée a été refusée. Pas encore exécuté dans le backend de production d’un client.

## Questions fréquentes

**Pourquoi un SDK serveur a-t-il besoin de sa propre clé ?**
Parce que c’est la seule autorisée à rattacher une conversion à un clic publicitaire. La clé web de @traqio/web est dans votre page, où tout le monde peut la lire : elle ne peut donc jamais rien attribuer. La clé serveur reste sur votre backend, est envoyée dans un en-tête Authorization et n’est conservée par Traqio que sous forme d’empreinte.

**Puis-je utiliser @traqio/node dans le navigateur ?**
Non. Il refuse de démarrer quand il trouve un document de navigateur, et il refuse une clé web. Dans le navigateur, utilisez @traqio/web, qui garde le trackID jusqu’à ce que la requête d’inscription le porte à votre backend.

**Et si le visiteur ne vient pas d’une publicité ?**
Passez un trackId null, ou aucun. L’inscription est quand même enregistrée, et l’utilisateur est organique : aucune campagne n’est créditée.

**Une nouvelle tentative compte-t-elle un achat deux fois ?**
Non. Chaque appel génère ses ids d’événement une seule fois et renvoie exactement le même corps, et un achat est aussi identifié par son transactionId : le même paiement signalé deux fois est stocké une fois.
