Tracking
POST /admin/tracking/conversions
Section titled “POST /admin/tracking/conversions”Postback de conversion d’affiliation (notification serveur-à-serveur du réseau d’affiliation, ou rejeu Talend). Brique du modèle de commission : le clic est compté côté public via /go/offer/{id} (voir docs/api.md), la conversion (vente confirmée) remonte ici.
Idempotent sur (targetType, externalRef) : un rejeu du même postback renvoie outcome: "duplicate" et ne modifie aucun agrégat (table tracking_conversion à clé unique).
Body (JSON)
{ "targetType": "offer", "targetId": "0f1e2d3c4b5a69788796a5b4c3d2e1f0", "externalRef": "ORDER-2026-00042", "amount": 12345, "commission": 617, "currency": "EUR", "occurredAt": "2026-06-16T10:00:00Z", "clickRef": "0193a1b2c3d4e5f60718293a4b5c6d7e"}| Champ | Type | Obligatoire | Sens |
|---|---|---|---|
targetType | string | oui | type de cible, valeur de l’enum TrackingTargetType (v1 : offer) |
targetId | string | oui | id de la cible (offre : 32 hex) |
externalRef | string | oui | id de commande/transaction côté réseau — clé d’idempotence |
amount | int | oui | valeur de la commande en unités mineures (centimes) |
commission | int | oui | notre commission en unités mineures |
currency | string | oui | code ISO-4217 (3 lettres, normalisé en majuscules) |
occurredAt | string | non | date de l’événement (ISO-8601 ou Y-m-d H:i:s) ; défaut = maintenant (UTC). Détermine le bucket tracking_daily. |
clickRef | string | non | subid renvoyé par le réseau (32 hex). Relie la conversion au clic précis (média + utilisateur) via tracking_click. Stocké tel quel ; un clickRef inconnu n’est pas une erreur (l’attribution reste best-effort). Résoluble ensuite via GET /admin/tracking/clicks/{ref}. |
Argent : tous les montants sont des entiers en unités mineures (centimes) — jamais de float. v1 suppose une seule devise par cible :
tracking_stats.currencygarde la dernière vue. Une cible facturée dans plusieurs devises mélangerait ses sommes — à scinder en v2 si le besoin apparaît.
Réponses
| Status | Body | Sens |
|---|---|---|
200 | { "status": "ok", "outcome": "recorded" } | nouvelle conversion enregistrée + agrégats bumpés |
200 | { "status": "ok", "outcome": "duplicate" } | externalRef déjà connu, no-op idempotent |
400 | { "error": "<raison>" } | body mal formé / champ manquant / devise invalide |
403 | { "error": "..." } | auth KO |
Exemple curl
curl -X POST \ -H "Authorization: Bearer $ADMIN_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"targetType":"offer","targetId":"0f1e2d3c4b5a69788796a5b4c3d2e1f0","externalRef":"ORDER-42","amount":12345,"commission":617,"currency":"EUR"}' \ http://hydrogen.dev.com/admin/tracking/conversionsGET /admin/tracking/{targetType}/{targetId}
Section titled “GET /admin/tracking/{targetType}/{targetId}”Agrégat cumulé (lifetime) d’une cible : clics, conversions, chiffre d’affaires et commission. Une cible sans activité renvoie 200 avec des compteurs à zéro (pas de 404) pour un état vide propre côté dashboard.
Path params
targetType: valeur deTrackingTargetType(v1 :offer).targetId: id de la cible.
Réponses
| Status | Body | Sens |
|---|---|---|
200 | { "targetType": "offer", "targetId": "<id>", "clicks": 123, "conversions": 4, "revenueAmount": 49380, "commissionAmount": 2469, "currency": "EUR" } | agrégat (montants en unités mineures) |
200 | { ..., "clicks": 0, "conversions": 0, "revenueAmount": 0, "commissionAmount": 0, "currency": null } | cible sans activité |
400 | { "error": "Unknown targetType." } | type inconnu |
403 | { "error": "..." } | auth KO |
curl -H "Authorization: Bearer $ADMIN_API_TOKEN" \ http://hydrogen.dev.com/admin/tracking/offer/0f1e2d3c4b5a69788796a5b4c3d2e1f0Notes
- Les clics ne sont pas comptés ici en direct : ils transitent par un tampon (
tracking_event) drainé par le workerbin/tracking-flush.php. Un clic met donc au plus un tick de worker à apparaître dans cet agrégat. - Les conversions sont écrites de façon synchrone (transaction) par le postback ci-dessus : elles sont immédiatement visibles.
GET /admin/tracking/clicks/{ref}
Section titled “GET /admin/tracking/clicks/{ref}”Résout un subid d’affiliation (clickRef) en son identité : qui a cliqué, depuis quel média, vers quelle cible. Contrepartie admin du subid que le réseau renvoie dans son postback de conversion.
Le clic est minté côté public par /go/offer/{id}/media/{mediaId} (voir docs/api.md) : un clickRef opaque (UUIDv7 en 32 hex) est persisté dans tracking_click puis ajouté à l’URL marchande comme paramètre subid. Seul ce clickRef opaque sort du système — aucun userId/PII n’est exposé au partenaire.
Path params
ref: leclickRefopaque (32 hex minuscule).
Réponses
| Status | Body | Sens |
|---|---|---|
200 | voir ci-dessous | clic résolu |
404 | { "error": "Click not found." } | ref mal formé ou inconnu |
403 | { "error": "..." } | auth KO |
{ "clickRef": "0193a1b2c3d4e5f60718293a4b5c6d7e", "targetType": "offer", "targetId": "0f1e2d3c4b5a69788796a5b4c3d2e1f0", "mediaId": "a1b2c3d4e5f600112233445566778899", "userId": "9f8e7d6c5b4a39281706f5e4d3c2b1a0", "visitorId": "0193a1b2c3d4e5f6071829aabbccddee", "createdAt": "2026-06-19T12:00:00+00:00"}| Champ | Sens |
|---|---|
mediaId | média visité (32 hex) — toujours présent (obligatoire à la génération du lien) |
userId | utilisateur connecté au clic (32 hex), null si anonyme |
visitorId | cookie visiteur longue durée (32 hex) corrélant les clics d’un même invité ; null si le cookie est désactivé/non consenti |
createdAt | horodatage du clic (ISO-8601) |
Notes
- Le clic est écrit synchrone avant le 302 (il doit exister avant qu’une conversion ne puisse le référencer), contrairement au compteur de clics par cible qui, lui, est bufferisé.
- Le cookie visiteur anonyme est désactivé par défaut (
TRACKING_VISITOR_COOKIE=false) : à n’activer que derrière le consentement RGPD. Sans lui, les clics anonymes ne sont pas reliés entre eux mais le reste de l’attribution fonctionne.