Skip to content

Tracking

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"
}
ChampTypeObligatoireSens
targetTypestringouitype de cible, valeur de l’enum TrackingTargetType (v1 : offer)
targetIdstringouiid de la cible (offre : 32 hex)
externalRefstringouiid de commande/transaction côté réseau — clé d’idempotence
amountintouivaleur de la commande en unités mineures (centimes)
commissionintouinotre commission en unités mineures
currencystringouicode ISO-4217 (3 lettres, normalisé en majuscules)
occurredAtstringnondate de l’événement (ISO-8601 ou Y-m-d H:i:s) ; défaut = maintenant (UTC). Détermine le bucket tracking_daily.
clickRefstringnonsubid 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.currency garde 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

StatusBodySens
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

Terminal window
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/conversions

GET /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 de TrackingTargetType (v1 : offer).
  • targetId : id de la cible.

Réponses

StatusBodySens
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
Terminal window
curl -H "Authorization: Bearer $ADMIN_API_TOKEN" \
http://hydrogen.dev.com/admin/tracking/offer/0f1e2d3c4b5a69788796a5b4c3d2e1f0

Notes

  • Les clics ne sont pas comptés ici en direct : ils transitent par un tampon (tracking_event) drainé par le worker bin/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.

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 : le clickRef opaque (32 hex minuscule).

Réponses

StatusBodySens
200voir ci-dessousclic 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"
}
ChampSens
mediaIdmédia visité (32 hex) — toujours présent (obligatoire à la génération du lien)
userIdutilisateur connecté au clic (32 hex), null si anonyme
visitorIdcookie visiteur longue durée (32 hex) corrélant les clics d’un même invité ; null si le cookie est désactivé/non consenti
createdAthorodatage 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.