J'ai plombé le tracking UTM end-to-end dans trois entreprises. Chaque fois, les mêmes cinq choses ont cassé dans le même ordre, et chaque fois la correction était la même : remontez le templating au niveau campagne, poussez le transfert de conversion vers le serveur, et mettez un dry run entre les deux. C'est l'essentiel de ce billet. Le reste est la checklist QA qui attrape les choses que vous n'aviez pas pensé à casser.
Vous n'avez pas besoin d'une Customer Data Platform pour ceci. Vous en aurez besoin un jour si votre problème d'attribution devient « assembler quatre touches anonymes à travers trois appareils en un seul parcours client », mais pour le cas que je vois le plus souvent - « tagger chaque lien sortant de manière cohérente, capturer le clic, transférer la conversion à Meta et GA4 côté serveur, et survivre à Safari » - un raccourcisseur d'URL avec templates plus une API conversions fait le boulot. Ci-dessous la version qui fonctionne, avec les modes de défaillance que j'ai vus appelés.
Ce qui va mal avec le tracking UTM#
Les marketeurs avec qui je travaille ne sont pas mauvais en UTMs. Le problème est que les outils défaillent à rendre facile de taper un UTM une fois, difficile d'en imposer un à travers une org, et impossible à corriger après lancement. Quatre modes de défaillance reviennent encore et encore.
Drift. Une personne tape utm_source=newsletter, une autre tape utm_source=Newsletter, une troisième tape utm_source=email. Six mois plus tard, votre canal « newsletter » est divisé en neuf variantes de chaîne dans GA4. Le nettoyer après coup est un exercice de regex-et-prière. Le script urchinTracker() original qui a introduit cette convention - le produit d'analytics web pré-Analytics Urchin de Google, open-sourcé brièvement en 2003 avant d'être absorbé - n'avait pas de couche de template non plus. La convention était toujours « tapez-le de manière cohérente » ; l'outillage ne l'a jamais imposé.
Tagging manuel à l'échelle. Une campagne de flyers avec 80 liens courts à travers quatre vitrines régionales c'est 320 URLs que vous devez taper, coller dans un tableur, copier dans votre raccourcisseur et prier. La moitié obtient le mauvais utm_content. Personne ne le remarque jusqu'à ce que la campagne soit lancée depuis deux semaines.
Trous de conversion côté serveur. Le pixel se déclenche sur la page de remerciement, GA4 le ramasse, Meta le ramasse, et vous rentrez chez vous. Puis Safari livre une autre version d'ITP, les installations de bloqueurs de pub augmentent, et vos conversions rapportées chutent d'un tiers. Les notes de release ITP 2.3 d'Apple parcourent exactement le mécanisme : la décoration de lien est étranglée, document.referrer est retiré, et tout flux analytics qui dépend du navigateur exécutant du JS tiers se dégrade silencieusement. Les conversions ont toujours lieu sur votre serveur. Elles ne parviennent simplement pas aux surfaces publicitaires.
Pas de dry-run. La première conversion qui passe par le nouveau pipeline est le premier vrai client. Si quelque chose est mal configuré, vous le découvrez trois jours plus tard quand l'algorithme d'optimisation a déjà retiré du budget d'une campagne qui fonctionnait réellement.
Ce billet adresse les trois premiers avec templates + import bulk + transfert côté serveur, et le quatrième avec une étape de vérification facile à sauter et coûteuse à sauter.
Templates UTM workspace et campagne#
Les templates poussent le problème de cohérence vers le haut de la stack. Vous définissez votre convention de tagging une fois au niveau workspace, ajoutez des surcharges par campagne là où c'est justifié, et laissez chaque lien hériter. Il ne reste aucun endroit où une faute de frappe puisse vivre.
Définissez d'abord les défauts du workspace. Les valeurs littérales fixent les variables qui ne changent jamais pour votre org (utm_medium = email pour les campagnes newsletter) ; les placeholders se remplissent depuis la payload du lien au moment de la création :
curl -X PUT \
https://api.elido.app/v1/workspaces/1/utm-template \
-H "Authorization: Bearer $ELIDO_TOKEN" \
-d '{
"utm_source": "{{ channel }}",
"utm_medium": "{{ medium }}",
"utm_campaign": "{{ campaign }}",
"utm_content": "{{ creative }}",
"utm_term": "{{ audience.segment }}"
}'
Quelques détails qui comptent ici :
- Les placeholders sont remplis à la création du lien, pas au moment du clic. Ce qui atterrit dans votre outil analytics est ce qui était voulu au moment où le lien a été minté - pas ce que la destination du lien a calculé au clic. Cela rend la reconstruction du journal d'audit beaucoup plus facile quand quelque chose semble faux six mois plus tard.
- Les placeholders inconnus échouent vite. Si votre import bulk manque d'une colonne
creativeet que le template workspace référence{{ creative }}, l'API retourne un 422 avec le nom de la variable non résolue. Pas d'application partielle silencieuse. - La référence complète du template, incluant les placeholders
link.tag.<name>qui lisent depuis le tableau de tags du lien (utile pour les agences multi-tenants qui ont besoin d'embarquer un identifiant client dans chaque URL), est dans le guide doc.
Puis layerez un template de campagne. Les campagnes héritent du workspace et remplacent le sous-ensemble spécifique à la campagne :
curl -X POST \
https://api.elido.app/v1/campaigns \
-H "Authorization: Bearer $ELIDO_TOKEN" \
-d '{
"name": "Spring 2026 - DACH",
"utm_template": {
"utm_campaign": "spring_2026_dach",
"utm_term": "{{ audience.locale }}"
}
}'
Tout ce qui n'est pas défini sur la campagne retombe sur les défauts du workspace. Le layering deux niveaux couvre la plupart des structures org réelles : conventions partagées au niveau workspace, surcharges spécifiques à l'équipe ou à la saison au niveau campagne. Si vous vous retrouvez à vouloir un troisième niveau d'héritage, c'est une odeur - généralement ça signifie que deux campagnes devraient être une seule campagne avec des valeurs de placeholder plus intelligentes.
Ce que les surcharges par lien sacrifient : une surcharge se déclenche indépendamment du template. Ce qu'elles gardent : la surcharge est enregistrée dans le journal d'audit avec acteur + timestamp + le diff résolu-vs-final. Dans six mois, quand quelqu'un demande pourquoi un lien dans une campagne de 200 liens a utm_term=manual_override, vous pouvez répondre.
Import bulk depuis Sheets - le workflow que la plupart des marketeurs utilisent réellement#
Les marketeurs ne s'assoient pas dans curl toute la journée. Le brief de campagne arrive comme un tableur avec des URLs de destination et des métadonnées de campagne, la deadline de lancement est vendredi, et la question est comment ce tableur devient 200 liens courts sans que quelqu'un tape la même chaîne UTM 200 fois.
Les noms de colonnes CSV correspondent aux noms de placeholder de votre template (insensible à la casse). Les colonnes qu'Elido ne reconnaît pas sont droppées avec un avertissement plutôt que copiées silencieusement - c'est délibéré. La copie silencieuse est comment vous vous retrouvez avec utm_brand_color apparaissant dans GA4 parce que quelqu'un a ajouté une colonne pour une note interne.
destination_url,channel,medium,creative
https://shop.example.com/de,newsletter,email,hero_a
https://shop.example.com/fr,newsletter,email,hero_a
https://shop.example.com/de,paid_social,meta,carousel_v2
https://shop.example.com/fr,paid_social,meta,carousel_v2
POSTez-le en multipart :
curl -X POST \
https://api.elido.app/v1/links/bulk \
-H "Authorization: Bearer $ELIDO_TOKEN" \
-F "csv=@launch_q2.csv" \
-F "campaign_id=cmp_8a2f"
Deux choses que ce flux de validation vous achète que l'UI un-lien-à-la-fois n'achète pas :
- Commit tout-ou-rien. Une seule mauvaise ligne abandonne tout l'upload et retourne les numéros de ligne offensants plus la raison -
row 47: unresolved variable {{ creative }}est une bien meilleure erreur que de découvrir à 16h un vendredi que 47 de vos 200 liens résolvent en chaîne placeholder. - Aperçu pré-lancement. La ligne de prévisualisation d'import bulk du dashboard montre l'URL résolue, incluant la chaîne de requête
utm_*rendue, avant le commit. Regardez le second lien pour vous assurer que votre template a fait ce que vous attendiez, puis regardez le dernier lien pour vous assurer que les lignes en bas du fichier n'ont pas dérivé. Deux coups d'œil, une minute.
Si votre tableur n'a pas une forme stable - les ordres de colonnes changent, les en-têtes sont renommés - le endpoint d'import bulk va être désagréable. La correction n'est pas dans notre outillage ; la correction est de s'engager sur un schéma CSV pour vos briefs de campagne et de traiter le drift de schéma comme un bug de processus. Nous discutons du pattern plus large dans la page solutions marketeurs.
Transfert de conversion côté serveur vers Meta CAPI et GA4#
L'attribution pixel-only perd 20-40 % des conversions à Safari ITP, aux bloqueurs de pub et aux bannières de consentement. Le nombre varie par industrie - l'ecommerce DTC voit le haut de la fourchette, le SaaS B2B le bas - mais chaque mesure que j'ai vue post-iOS 14 met la fiabilité du pixel bien en dessous de la marque des 95 % que les plateformes publicitaires supposent. L'algorithme d'optimisation reçoit des entrées plus bruyantes et votre CPA semble pire qu'il ne l'est.
La documentation Conversions API de Meta est explicite à ce sujet : les événements serveur sont ce que vous voulez, le pixel côté navigateur est le supplément. Le Measurement Protocol de GA4 fait le même cas. Les deux protocoles acceptent la même forme : un événement côté serveur avec les détails de conversion, un event_id pour la déduplication, et idéalement des identifiants utilisateur hachés pour que les plateformes puissent assembler la conversion à un visiteur connu.
La plomberie qui ferme le trou est mécanique. Trois étapes.
Étape un - capturer le click_id. Chaque réponse de redirection Elido porte un header X-Elido-Click-Id. Les SDKs TS / Python / Go le font apparaître sur l'objet de réponse de redirection ; HTTP brut fonctionne aussi :
curl -sI https://elido.me/launch | grep -i click-id
# X-Elido-Click-Id: clk_01HYZ7T8WV6KQX3M
Cachez-le dans un cookie de première partie sur la page de destination (elido_click_id, TTL 90 jours - assez long pour couvrir une évaluation SaaS typique, assez court pour satisfaire les directives ePrivacy). Relisez-le au checkout.
Étape deux - câbler les destinations. PUTez les identifiants pour les surfaces vers lesquelles vous voulez transférer. N'importe quel sous-ensemble fonctionne ; les surfaces manquantes sont sautées silencieusement :
curl -X PUT \
https://api.elido.app/v1/workspaces/1/conversion-forwarding \
-H "Authorization: Bearer $ELIDO_TOKEN" \
-d '{
"meta_capi": {
"pixel_id": "1234567890",
"access_token": "EAA…",
"test_event_code": null
},
"ga4_mp": {
"measurement_id": "G-ABC123",
"api_secret": "abc_def_ghi"
},
"mixpanel": {
"project_token": "pm_…",
"service_account": "[email protected]"
}
}'
Étape trois - POSTer la conversion. Quand la commande se déclenche, envoyez l'événement avec le click_id et les détails de la commande. event_id est votre clé d'idempotence :
curl -X POST \
https://api.elido.app/v1/conversions \
-H "Authorization: Bearer $ELIDO_TOKEN" \
-d '{
"click_id": "clk_01HYZ7T8WV6KQX3M",
"event_name": "purchase",
"event_id": "ord_98231",
"value": 89.00,
"currency": "EUR",
"user": {
"email": "[email protected]",
"phone": "+4915123456789",
"external_id": "cust_5128"
}
}'
Les champs d'identité utilisateur sont hachés SHA-256 avant transfert à Meta et GA4 - c'est ce que les deux plateformes exigent. Le contexte UTM est tiré de la ligne de clic qui correspond à click_id, donc l'événement transféré porte l'attribution de campagne originale même si l'utilisateur a erré sur le site pendant une heure avant de passer au checkout. La mécanique complète, incluant la gestion des remboursements et le toggle de modèle d'attribution multi-touch, est dans le guide de transfert de conversion.
C'est l'essentiel du trou fermé. Il y a un trou résiduel - les visiteurs qui bloquent le cookie click_id, ou arrivent par un chemin non-Elido - mais pour les campagnes vers lesquelles vous pilotez réellement du trafic, vous êtes passé de « 60-80 % de fiabilité pixel » à « 95 %+ de fiabilité serveur ».
Trois cas limites sur lesquels le journal d'audit vous sauvera#
Les templates et le transfert gèrent le happy path. Les cas ci-dessous apparaissent à la semaine trois de toute campagne non triviale, et la bonne réponse à toutes vit dans le journal d'audit + le panneau conversions - pas dans la tentative de concevoir un template plus élaboré.
Remboursements. Une conversion d'achat s'est déclenchée, le client a retourné l'article une semaine plus tard, et votre revenu rapporté est maintenant 8 % trop élevé. La correction est de POSTer le même event_id avec event_name: "refund". Meta et GA4 traitent ceci comme une conversion négative contre l'original ; Mixpanel l'enregistre comme un événement séparé que vous soustrayez dans votre funnel. La raison pour laquelle event_id est shapé ainsi : l'idempotence au niveau event id signifie que vous ne pouvez pas non plus double-compter le remboursement. Le pattern complet est documenté dans la section cas limites du guide de transfert de conversion - remboursements, remboursements partiels et avoirs ont chacun une forme légèrement différente.
Misses de click-id. Une conversion se déclenche avec un click_id qui ne correspond à aucun clic connu - faute de frappe, expiré au-delà de la rétention, mauvais workspace. La conversion est toujours enregistrée contre le workspace mais transférée avec un contexte UTM vide. C'est intentionnel : l'attribution catch-all est plus utile que de droper la conversion par terre, et le flag click_id_unknown dans le journal d'audit vous permet de filtrer la tranche non attribuée lors du reporting. Si la tranche est plus grande que 5 % des conversions, quelque chose ne va pas avec la façon dont vous persistez le click_id sur la page de destination - généralement l'attribut SameSite du cookie ou la portée du chemin.
Conversions arrivant tardivement. Une vente SaaS B2B se ferme 47 jours après le clic original. La rétention de clic par défaut d'Elido est de 30 jours, donc au moment où la conversion se déclenche, le clic a expiré et vous êtes dans le cas miss-click-id ci-dessus. Deux corrections, selon votre cycle de vente : augmentez la rétention à 90 jours sur le workspace (plan Pro et au-dessus), ou capturez le click_id sur un identifiant de première partie longue durée (la colonne original_click_id de votre enregistrement client) pour que vous puissiez le réassembler au moment de la conversion même si le cookie a disparu. Nous avons vu les deux patterns en production.
Le journal d'audit montre le diff UTM résolu-vs-final par lien, le code de réponse de transfert par destination par conversion, et l'état de jointure click_id-vers-conversion. Quand l'algorithme d'optimisation retire du budget d'une campagne qui semble sous-performante, le journal d'audit est ce qui vous permet de dire « non, la campagne va bien ; nous avons perdu trois jours de transfert à cause d'une rotation d'api_secret GA4 ». Regardez-le.
Le QA pré-lancement - dry-run le pipeline entier#
Ne laissez pas la première conversion qui passe par ceci être un vrai client. Le coût d'un dry run de 30 minutes est entièrement supporté par vous ; le coût d'un pipeline mal configuré est supporté par l'algorithme d'optimisation qui retire du budget de votre meilleure campagne pendant deux jours avant que vous ne le remarquiez. L'asymétrie est mauvaise.
Trois étapes, dans l'ordre.
Dry run d'import bulk. Le endpoint d'import bulk accepte dry_run=true comme paramètre de requête. Il exécute la validation, résout les templates, et retourne les liens qui auraient été créés sans commiter. Ouvrez la réponse dans n'importe quel viewer JSON ; l'URL résolue de chaque ligne est visible. Vérifiez par échantillon 3-5 lignes : le second lien, le dernier lien, et toute ligne qui a surchargé les défauts du workspace. Vérifiez que la chaîne de requête utm_* est exactement ce que dit votre brief de campagne.
Mode test de transfert de conversion. Meta CAPI accepte un paramètre test_event_code, qui route l'événement dans l'onglet Test Events dans Events Manager au lieu de la production. Définissez-le sur la config de transfert du workspace, envoyez 10-20 conversions d'échantillon, et confirmez qu'elles atterrissent. Même idée pour GA4 : définissez debug_mode: true sur les événements et vérifiez dans DebugView. Les deux sont en temps réel. Le point n'est pas de vérifier par échantillon que l'API fonctionne ; le point est d'attraper un pixel_id mal configuré ou un api_secret qui a été tourné et jamais mis à jour.
Smoke end-to-end. Cliquez sur l'un de vos vrais liens courts depuis une session navigateur propre. Observez le clic dans le panneau recent-clicks du dashboard Elido. Faites semblant d'acheter quelque chose - POSTez une conversion purchase avec ce click_id depuis votre terminal. Confirmez que la conversion apparaît dans Meta Test Events et GA4 DebugView avec le bon contexte UTM attaché. Toute la boucle prend moins de 10 minutes une fois que vous l'avez faite une fois.
Après que les trois passent, droppez le test_event_code, définissez debug_mode: false, et livrez. Le premier vrai client aura un pipeline propre qui l'attend.
Quand vous voudriez vraiment un CDP#
Templates plus import bulk plus transfert côté serveur vous mène la majeure partie du chemin. Il y a une classe de problèmes où ça ne le fait pas, et atteindre un CDP est le bon choix.
Stitching d'identité cross-device. Un visiteur clique sur un lien sur mobile, ne convertit pas, revient sur desktop, s'inscrit. Vous voulez que les deux touches soient attribuées à la même personne. Le tracking UTM + click_id est au niveau touche ; la couche d'identité utilisateur qui fait des deux touches un seul parcours est ce pour quoi un CDP (Segment, mParticle, RudderStack) est construit. Elido stocke jusqu'à 30 jours de clics par visiteur et supporte l'attribution last-touch / first-touch / position-based dans cette fenêtre, mais la jointure cross-device a besoin d'un graphe d'identité que nous n'opérons délibérément pas.
Personnalisation sub-100ms. Si vous rendez la page de destination en fonction des touches précédentes du visiteur en temps réel - tirant le cohort depuis un feature store et variant le headline du hero - vous avez besoin de la résolution d'identité proche du rendu. C'est le territoire CDP ou, plus souvent, une plateforme d'expérimentation comme PostHog ou LaunchDarkly layerée au-dessus.
Attribution multi-touch à l'échelle. Last-touch est bien pour la plupart des campagnes. Si votre cycle de vente a six touches sur quatre mois et que vous avez vraiment besoin de créditer chacune, vous êtes dans le territoire où l'attribution chaîne-de-Markov ou valeur-de-Shapley commence à compter. Elido fait last-touch / first-touch / position-based ; quelque chose de plus sophistiqué veut un outil avec un graphe d'identité approprié et une couche de modèle.
Pour tout le reste - et « tout le reste » est la plupart des équipes marketing avec qui j'ai travaillé - le pattern templates + import bulk + transfert côté serveur est suffisant. Configurez le template workspace une fois, le template campagne par lancement, la config de transfert une fois par intégration de plateforme, et lancez le dry run avant chaque lancement. Si vous faites les quatre, vous aurez un pipeline UTM plus serré que 80 % des équipes marketing que j'ai auditées.
Construisez-le une fois, dry-runnez-le avant chaque lancement, et passez à la campagne suivante.
Pour aller plus loin sur le blog#
Essayer Elido
Collez une URL, obtenez un lien court
Sans inscription. Lien actif 30 jours. Inscrivez-vous pour le garder pour toujours.
Gratuit, sans inscription · 2 par jour