Se connecter au compte par l'API, poser ses pixels, envoyer ses conversions serveur, construire ses audiences, structurer ses campagnes, publier ses annonces, et surtout vérifier que la chaîne dit la vérité. Tout ce qui se passe entre le clic et le chiffre affiché dans le rapport.
Trois objets, trois niveaux, et presque toutes les erreurs de débutant viennent de leur confusion.
La campagne porte l'objectif : ce que tu demandes à Meta d'aller chercher. L'ensemble de pubs (adset) porte l'argent et les gens : le budget, le ciblage, le calendrier, l'optimisation. L'annonce porte la créa : la vidéo ou l'image, le texte, le bouton, le lien.
Conséquence pratique qui revient tout le temps : on ne change pas d'audience au niveau d'une annonce, et on ne change pas de vidéo au niveau d'un ensemble. Quand quelqu'un dit « change l'audience de cette pub », il parle en réalité de l'ensemble qui la contient, donc de toutes ses annonces sœurs.
Le pixel est un bout de code sur ton site qui raconte à Meta ce que font les visiteurs. L'API Conversions raconte la même chose, mais depuis ton serveur. Les deux ensemble forment ta mesure : le navigateur voit le geste, le serveur voit la vérité.
Le créatif (adcreative) est un objet à part, réutilisable : c'est le paquet vidéo + texte + bouton + lien. Une annonce ne fait que pointer vers un créatif. Ça a une conséquence énorme : un créatif est immuable. Pour changer un texte ou un lien, on crée un nouveau créatif et on le rattache à l'annonce. L'annonce, elle, garde son identité et son historique.
Objectif = campagne. Argent et gens = ensemble. Créa = annonce. Le créatif ne se modifie jamais, il se remplace.
Trois façons d'obtenir un jeton d'accès. Deux sont des impasses, une seule tient dans la durée.
C'est celui que tu récupères en deux clics dans l'explorateur d'API. Il expire au bout d'une heure ou de deux mois, il meurt quand tu changes ton mot de passe, et il porte tes droits personnels. Utile pour tester une requête, inutilisable pour un système qui tourne.
Une application Meta commence sa vie en mode développement. Ses jetons lisent souvent correctement, ce qui donne l'illusion que tout va bien, mais échouent à l'écriture avec des messages absurdes du genre « Invalid parameter » ou un code d'erreur 1885183. Tu passes une heure à chercher une erreur de syntaxe qui n'existe pas.
Si tes lectures marchent et que toutes tes écritures échouent, ne cherche pas dans ton code. Regarde d'abord si l'application est en mode développement ou en direct. Une application en développement ne peut pas créer de créatif publicitaire.
Dans le gestionnaire d'entreprise, tu crées un utilisateur système : un compte robot qui n'est lié à aucune personne physique. Tu lui donnes accès aux ressources dont il a besoin, tu génères son jeton, et ce jeton n'expire pas. Il survit aux départs, aux changements de mot de passe et aux vacances.
Les droits à cocher, ni plus ni moins : ads_management pour créer et modifier, ads_read pour lire les rapports, business_management pour les audiences et les ressources, pages_read_engagement et pages_show_list si tu publies au nom d'une Page, instagram_basic si tu touches à Instagram.
Chaque nouvelle ressource (un compte publicitaire, une Page, un pixel, un catalogue) doit être explicitement assignée à l'utilisateur système. Ce n'est pas automatique. Le symptôme est toujours le même : « ça marchait, on a ajouté un pixel, et depuis ça ne marche plus ».
# qui suis-je, et jusqu'à quand ?
curl -s "https://graph.facebook.com/v21.0/me?access_token=$TOKEN"
curl -s "https://graph.facebook.com/v21.0/debug_token?input_token=$TOKEN&access_token=$TOKEN"
# à quoi ai-je accès ?
curl -s "https://graph.facebook.com/v21.0/me/adaccounts?fields=name,account_status&access_token=$TOKEN"
Dans la réponse de debug_token, regarde expires_at : s'il vaut 0, le jeton est permanent, c'est ce que tu veux. Regarde aussi scopes : si une permission manque, tu la verras ici avant de la découvrir en pleine écriture.
Un seul jeton maître, rangé dans un fichier de secrets hors du dépôt de code, référencé par une variable d'environnement. Le jour où il faut le renouveler, tu changes un endroit. Et tu notes quelque part ce que chaque clé sert à faire : dans six mois, personne ne saura.
Le pixel est simple à coller et facile à mal poser. Voici ce qui compte vraiment.
Si tu vends la même chose à deux marchés très différents (par exemple deux pays, ou deux langues), donne à chacun son pixel. Chaque pixel accumule son propre apprentissage, et une campagne optimise sur les conversions de son pixel. Un pixel unique mélange les signaux et t'empêche de savoir quel marché fonctionne.
À l'inverse, si c'est le même public, garde un seul pixel : le découpage se fait par évènement, pas par pixel.
<script>
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
document,'script','https://connect.facebook.net/en_US/fbevents.js');
fbq('init','TON_PIXEL_ID');
fbq('track','PageView');
</script>
Ce bout de code fait deux choses : il crée une file d'attente (n.queue) et il charge le script réel de façon asynchrone. La file d'attente est la partie importante : elle te permet d'appeler fbq(...) immédiatement, même si le script n'est pas encore arrivé. Les appels sont mis en réserve et rejoués au chargement.
Sur un site classique, chaque page rechargée envoie son PageView toute seule. Sur un site qui change d'écran sans recharger, Meta ne voit qu'une seule page pour toute la visite. Il faut renvoyer un PageView à chaque changement d'URL, sinon ton audience « visiteurs de la page de vente » restera désespérément vide.
Dans le <head>, le plus haut possible, mais après le bandeau de consentement si tu en as un. Un pixel chargé avant l'accord de l'utilisateur est un problème juridique, pas un détail technique.
Installe l'extension Meta Pixel Helper dans ton navigateur, ouvre ton site, elle liste les évènements partis. Puis, dans le gestionnaire d'évènements de Meta, ouvre l'onglet Tester les évènements : tu y vois arriver en direct ce que le navigateur ET le serveur envoient. C'est le seul endroit où tu vois les deux côtés en même temps.
1. Le testeur d'évènements montre bien l'évènement, avec ses paramètres et le bon domaine.
2. Le rapport de correspondance affiche des champs remplis, pas seulement l'adresse IP.
3. Une requête directe à l'API Conversions te répond events_received: 1.
Et surtout : ne juge jamais un pixel sur le compteur de la page d'accueil du gestionnaire. Il est en retard, il agrège, et il t'a déjà fait conclure trois fois qu'un pixel était cassé alors qu'il tournait très bien.
C'est le chapitre que tout le monde saute, et c'est celui qui détermine si tes campagnes apprendront quelque chose.
Meta a un catalogue d'évènements standard (PageView, Lead, Purchase, CompleteRegistration, Schedule…). Ils sont reconnus partout, optimisables directement, et compris par tous les outils.
Un évènement personnalisé, c'est n'importe quel nom que tu inventes. On l'envoie avec trackCustom et non track, sans quoi Meta affiche un avertissement. Il reste parfaitement optimisable : dans l'ensemble de pubs, tu choisis « conversion personnalisée » et tu désignes ton nom.
Utilise un évènement standard quand le sens colle exactement (un achat est un Purchase). Utilise un nom personnalisé dès que tu as besoin de distinguer des choses que Meta confondrait : deux marchés, deux offres, deux étapes de qualification. Un nom explicite comme RdvPris_Maroc vaut mieux qu'un Lead générique que tu ne pourras plus découper.
Chaque nom d'évènement porte des mois d'apprentissage. Le renommer, c'est repartir de zéro : Meta ne fait aucun lien entre l'ancien et le nouveau. Si un nom est moche mais qu'il a de l'historique, garde-le et documente-le.
Un évènement nu dit « quelqu'un a acheté ». Un évènement paramétré dit « quelqu'un a acheté pour 2 500 € ». La différence est énorme : sans valeur, Meta ne peut pas optimiser sur le chiffre d'affaires, et ton retour sur investissement affiché reste à zéro.
fbq('track','Purchase', {value: 2500, currency:'EUR'});
fbq('trackCustom','RdvPris_Maroc', {content_name:'appel-decouverte'});
Les ventes remontaient bien, mais sans valeur : Meta ne pouvait pas privilégier les gros contrats, et le rapport affichait un retour nul alors que le compte était rentable. Pire, Meta garde le premier évènement reçu pour un identifiant donné : corriger le code ne répare pas le passé, seulement l'avenir. Vérifie la valeur dès le premier jour.
Un schéma qui tient : Action_Marché ou Étape_Segment. Écris la liste quelque part, avec pour chaque évènement : ce qu'il signifie exactement, à quel moment il part, qui l'envoie (navigateur ou serveur), et sur quel pixel. Sans ce document, dans trois mois personne ne saura si Lead2 veut dire « a rempli le formulaire » ou « a été qualifié ».
Le navigateur perd des évènements : bloqueurs de publicité, réglages de confidentialité d'Apple, onglet fermé trop vite, connexion coupée. Selon les publics, tu perds entre 15 et 40 % de ce qui s'est réellement passé. L'API Conversions envoie les mêmes évènements depuis ton serveur, où rien ne bloque.
On envoie le même évènement des deux côtés, avec le même identifiant. Meta rapproche les doublons et n'en garde qu'un. On gagne la fiabilité du serveur sans perdre les signaux riches du navigateur.
POST https://graph.facebook.com/v21.0/{PIXEL_ID}/events
{
"data": [{
"event_name": "Purchase",
"event_time": 1785312000,
"event_id": "cmd-8842", // la clé de la déduplication
"action_source": "website",
"event_source_url": "https://exemple.com/merci",
"user_data": {
"em": [""],
"ph": [""],
"fn": [""],
"ln": [""],
"country": [""],
"external_id": [""],
"fbc": "fb.1.1785311000.IwAR...", // en clair, jamais haché
"fbp": "fb.1.1785310000.1234567890" // en clair, jamais haché
},
"custom_data": { "value": 2500, "currency": "EUR" }
}],
"access_token": "..."
}
1. Hacher fbc et fbp : ils partent en clair, ce sont des identifiants Meta, pas des données personnelles.
2. Oublier de normaliser avant de hacher : email en minuscules et sans espaces, téléphone au format international sans le +, prénom et nom en minuscules sans accents.
3. Envoyer une chaîne vide hachée. Un champ absent vaut mieux qu'un champ vide : le vide pollue ton score de correspondance.
Meta considère deux évènements comme identiques s'ils ont le même event_name et le même event_id, dans une fenêtre de quelques jours. D'où la règle : ton identifiant doit être stable et déterministe, dérivé de la chose elle-même (le numéro de commande, l'identifiant du rendez-vous), jamais d'un tirage au hasard ni d'un horodatage.
Génère-le côté serveur, passe-le au navigateur, et envoie exactement le même des deux côtés. Si tu génères deux identifiants différents, tu comptes tout en double, et ton coût par acquisition paraît deux fois meilleur qu'il ne l'est. C'est une erreur qui se voit rarement et qui fausse toutes les décisions.
Le navigateur voit « il a cliqué sur réserver ». Ton système, lui, sait « le rendez-vous existe vraiment dans l'agenda ». Ce n'est pas la même chose : un clic peut ne rien donner, une page peut être rechargée, un lien partagé.
Optimise toujours sur l'évènement serveur, celui qui n'existe que si la chose a vraiment eu lieu. Garde l'évènement navigateur comme signal secondaire, utile aux audiences. Un signal navigateur peut mentir, une ligne dans ta base de données non.
Deux détails qui font perdre des heures. D'abord, event_time est en temps universel, en secondes : si ton serveur pense en heure locale, tes évènements arrivent décalés et sortent de la fenêtre d'attribution. Ensuite, si ton système peut annuler une conversion (un rendez-vous annulé, une commande remboursée), assure-toi que l'annulation ne parte pas comme une conversion réussie. Un vrai cas vécu : sept annulations parties comme des présences, et vingt-cinq faux évènements impossibles à retirer de chez Meta.
Meta note la qualité de tes évènements sur dix. Ce score dit une chose : à quel point Meta arrive à relier tes évènements à de vraies personnes. Un score faible, c'est de l'argent gaspillé, parce que Meta ne sait pas à qui ressemblent tes acheteurs.
L'email d'abord : c'est le plus discriminant. Puis le téléphone. Puis external_id, ton propre identifiant client, très sous-estimé : il permet à Meta de recoller plusieurs évènements de la même personne. Ensuite prénom, nom, ville, pays. Enfin fbc et fbp, qui viennent du clic et du cookie.
fbp est le cookie que le pixel dépose. fbc encode le clic publicitaire, à partir du paramètre fbclid ajouté par Meta à l'URL. Quand tu l'as, l'attribution devient quasi certaine.
Certaines agences ajoutent &fbclid={{fbclid}} dans les paramètres d'URL des annonces, en croyant bien faire. Cette macro n'existe pas chez Meta, qui ajoute déjà fbclid tout seul. Résultat : la chaîne {{fbclid}} arrive telle quelle dans l'URL, le pixel la recopie dans le cookie, et le cookie reste empoisonné pendant 90 jours.
Le correctif tient en deux gestes : retirer le paramètre de toutes les annonces, et refuser côté serveur toute valeur contenant {{. Sur un compte réel, ça a fait passer le nombre de rendez-vous correctement attribués de 82 à 98 sur 200.
Prends tes cent dernières conversions et compte, champ par champ, combien sont remplis. Tu obtiens un tableau du genre : email 100 %, téléphone 96 %, prénom 100 %, ville 88 %, fbc 67 %. Tu vois immédiatement où tu perds.
Les gains les plus rapides, dans l'ordre : ajouter external_id partout (c'est gratuit, tu l'as déjà), récupérer fbc et fbp au moment du formulaire et les stocker avec le prospect (pas seulement au moment de la conversion), et normaliser les téléphones au format international avant de les hacher.
Le score de correspondance n'est pas exposé par l'API. Il se lit uniquement dans le gestionnaire d'évènements, onglet du pixel. En revanche, tu peux calculer toi-même le taux de remplissage de chaque champ, et c'est bien plus actionnable qu'une note sur dix.
C'est le chapitre le plus important du guide, et celui où j'ai commis la faute la plus grave. Une audience mal réglée transforme un retargeting chirurgical en publicité de masse, sans qu'aucun voyant ne s'allume.
Quand tu crées un ensemble de pubs avec des audiences personnalisées, Meta active par défaut une option appelée « audience Advantage+ ». Elle transforme ton ciblage en simple suggestion : Meta se donne le droit de diffuser en dehors de ton audience quand il pense que c'est plus efficace.
Sur une campagne de notoriété, c'est discutable mais défendable. Sur du retargeting, c'est absurde : tu paies pour parler à des gens qui ne te connaissent pas, avec un message écrit pour ceux qui te connaissent.
Dans l'interface, l'ensemble affiche « audience suggérée » au lieu de « audience utilisée ». En API, tu ne vois rien : tes audiences sont bien listées dans le ciblage, tout paraît normal.
Le réglage à poser :
"targeting": {
"custom_audiences": [{"id": "..."}],
"excluded_custom_audiences": [{"id": "..."}],
"targeting_automation": {"advantage_audience": 0}
}
Et le point qui fait mal : ce champ ne s'accepte qu'à la création de l'ensemble. En modification, l'API répond « Cannot disable Advantage options ». Un ensemble créé sans ce réglage doit être recréé, ou corrigé à la main dans l'interface. Vérifie-le sur chaque ensemble avant de lancer quoi que ce soit.
Basée sur le pixel. Le piège : la règle par défaut est « tous les visiteurs », ce qui veut dire tous les sites où ce pixel est posé. Si tu as gardé le pixel sur un ancien domaine, ou s'il traîne sur une page de test, ces gens entrent dans ton audience.
Un pixel voyait encore 1 225 visites par mois sur l'ancien site d'une marque rebaptisée, plus du trafic de développement en local. Les audiences « visiteurs » mélangeaient tout. Le correctif : filtrer sur l'URL.
"filter": {"operator":"and","filters":[
{"field":"url","operator":"i_contains","value":"tondomaine.com"}
]}
Pour vérifier ce que ton pixel voit vraiment, demande-lui ses statistiques par URL : tu découvriras souvent des domaines que tu avais oubliés.
Très puissantes en haut de funnel : quelqu'un qui a regardé trois secondes d'une de tes pubs te connaît déjà un peu. Les seuils utiles sont 3 secondes (large, bon pour du volume) et 10 secondes ou 15 secondes (plus qualifié).
1. Une vidéo envoyée par l'API au compte publicitaire n'est pas rattachée à ta Page. Meta la refuse dans une audience de vues vidéo (« isn't associated with a Page »). Les créas dont tu veux retargeter les spectateurs doivent passer par la Page.
2. L'interface propose de choisir « toute la Page » comme source, ce qui capterait toutes les vidéos d'un coup. L'API ne le permet pas : elle exige la liste des vidéos une par une. Si tu tiens à la source Page, c'est un passage obligé par l'interface.
Deuxième réflexe indispensable : ouvre la règle de tes audiences de vues existantes et vérifie quelles vidéos elles contiennent. Sur un compte repris d'une agence, on y trouve souvent des vidéos d'annonces archivées depuis un an, voire des vidéos d'une autre marque du même gestionnaire.
Tu envoies tes emails et téléphones hachés. Deux usages : exclure tes clients de toutes tes campagnes d'acquisition, et créer des audiences similaires à partir de tes meilleurs acheteurs.
POST /{AUDIENCE_ID}/users
payload = {"schema":"EMAIL_SHA256","data":[[""],[""]]}
Un seul type par envoi (EMAIL_SHA256, puis PHONE_SHA256 dans un second appel) est le chemin le plus simple. Les combinaisons multi-colonnes existent mais leurs noms sont stricts et mal documentés : commence simple.
C'est ce qui te permet de construire des étages fins : « a vu 70 % de la vidéo de vente », « a pris rendez-vous », « est venu au rendez-vous ». Elles se construisent sur le nom d'évènement, avec une fenêtre de rétention.
"rule": {"inclusions":{"operator":"or","rules":[{
"event_sources":[{"id":"PIXEL_ID","type":"pixel"}],
"retention_seconds": 1209600,
"filter":{"operator":"and","filters":[
{"field":"url","operator":"i_contains","value":"tondomaine.com"},
{"operator":"or","filters":[
{"field":"event","operator":"eq","value":"View70%"},
{"field":"event","operator":"eq","value":"View75%"}
]}
]}
}]}}
Si ton site a changé d'outil au fil du temps, le même évènement peut exister sous deux orthographes (par exemple View70% et sa version encodée View70%25). Mets les deux dans la règle : sinon tu perds la moitié de ton audience sans le voir.
Une audience d'inclusion dit à qui tu parles. Les exclusions disent à qui tu arrêtes de parler, et elles valent souvent plus cher que les inclusions.
Les trois exclusions qui devraient être sur presque tous tes ensembles :
Chaque étage exclut l'étage du dessus. C'est ce qui fait qu'une personne ne voit qu'un seul message à la fois, celui qui correspond à son état, et qu'elle progresse toute seule d'un étage à l'autre au fil de ses actions.
En dessous de mille personnes, Meta masque le chiffre et affiche un avertissement de portée limitée. Ce n'est pas bloquant, mais ça change ta stratégie : sur de petites audiences, l'algorithme n'a pas assez de matière pour apprendre, donc les tests fins ne veulent rien dire et les budgets doivent rester modestes. Sur ces étages-là, tu pilotes à la main.
Une bonne structure, c'est une structure où chaque ligne du rapport répond à une question. Si tu ne sais pas quelle décision tu prendras en regardant une ligne, elle n'a pas lieu d'exister.
L'objectif de campagne détermine ce que Meta va chercher. La règle est bête et elle est presque toujours violée : demande exactement l'action que tu veux. Si tu veux des rendez-vous, optimise sur l'évènement de rendez-vous. Pas sur les clics, pas sur la couverture, pas sur les vues de page.
La couverture ne se justifie que dans un cas : quand tu veux simplement être vu par une petite audience déjà acquise, sans lui demander d'agir tout de suite. Par exemple, occuper l'esprit de quelqu'un qui a déjà un rendez-vous prévu. Partout ailleurs, c'est de la conversion.
Avec un budget de campagne, Meta répartit lui-même entre les ensembles. C'est excellent quand tes ensembles se ressemblent et qu'ils sont grands : l'algorithme trouve le meilleur plus vite que toi.
C'est un piège quand tes ensembles sont de tailles très différentes. Meta va toujours vers l'impression la moins chère : un ensemble à soixante mille personnes mangera tout le budget, et tes trois cents prospects les plus chauds ne verront jamais rien. Dans ce cas, budget par ensemble, fixé à la main.
Ensembles comparables et volumineux, tu veux de la vitesse : budget de campagne. Ensembles de tailles très inégales, chacun avec un métier différent : budget par ensemble.
La plupart des comptes ont un ensemble de retargeting, qui empile toutes les audiences et sert le même message à tout le monde. C'est confortable et c'est du gâchis : celui qui a vu trois secondes de pub et celui qui a raté son rendez-vous reçoivent le même argument.
Une structure qui marche découpe le retargeting selon où en est la personne dans sa tête, pas selon le type de créa :
| Étage | Qui | Ce qu'on lui montre | Où on l'envoie |
|---|---|---|---|
| 01 | a vu la pub, n'a pas cliqué | un nouvel angle, un nouveau hook | la page de vente |
| 02 | a vu la page de vente, n'a pas réservé | les objections, les preuves | la page de réservation |
| 03 | a réservé, n'est pas encore venu | de la confiance, des coulisses | des témoignages, une chaîne vidéo |
| 04 | est venu, n'a pas signé | les objections précises entendues en rendez-vous | des témoignages, ou un contact direct |
| 05 | a réservé et n'est pas venu | une relance douce, une deuxième chance | la page de réservation |
Ne renvoie jamais quelqu'un vers l'étape qu'il vient de vivre. J'avais construit une échelle où celui qui avait regardé 70 % de la vidéo de vente était renvoyé… sur la vidéo de vente. Et celui qui était venu au rendez-vous aussi. Chaque étage doit pousser vers l'étape suivante : celui qui a vu la vidéo va choisir un créneau, celui qui est venu au rendez-vous regarde des clients parler.
Variante qui fonctionne très bien : doubler l'échelle par une deuxième campagne, découpée sur exactement les mêmes étages, mais qui ne montre que des preuves (résultats de clients, captures de messages, témoignages). Seul le bouton change d'un étage à l'autre.
Le prospect reçoit en alternance un argument et une preuve. L'argument ouvre, la preuve verrouille. Et opérationnellement, une nouvelle preuve se publie à un seul endroit au lieu de cinq.
Deux écoles, et le choix dépend de ce que tu veux apprendre.
Les créas dynamiques : tu mets plusieurs visuels, plusieurs titres et plusieurs textes dans une seule annonce, et Meta assemble la combinaison la plus efficace par personne. C'est rapide et c'est efficace quand tu as du volume. Le prix à payer : la lisibilité. Le rapport te donne bien une ventilation par élément, mais pas les combinaisons, et sur de petits volumes ces chiffres ne veulent rien dire.
Une annonce par créa : plus lent à apprendre, mais tu lis ligne par ligne quelle vidéo et quelle preuve font réserver. Tu coupes et tu montes à la main.
Dynamique sur le froid, là où il y a du budget et une large audience : Meta a de quoi apprendre. Une annonce par créa sur le retargeting, là où les audiences sont petites et où la vraie question est « laquelle de mes preuves fait bouger les gens ». Sur de petits volumes, l'algorithme n'apprendra rien de toute façon : autant garder la lecture.
Adopte un schéma et ne le lâche jamais. Par exemple MARQUE-TYPE-ÉTAGE-CRÉA. Ça paraît cosmétique jusqu'au jour où tu cherches pourquoi une ligne performe et où tu ne sais plus ce qu'elle contient.
Et surtout, les paramètres d'URL. Chaque annonce doit porter les siens, sinon ton outil de mesure interne ne saura jamais d'où viennent les gens :
utm_source=meta&utm_medium=paid&utm_campaign=marque-retargeting&utm_content=etage02&utm_term=nom-de-la-crea
La campagne pour le grand ensemble, utm_content pour l'étage, utm_term pour la créa précise. Avec ça, tu lis dans ton propre outil ce que Meta ne te dira jamais : quelle vidéo a produit un client, pas seulement un clic.
La chaîne est toujours la même : on téléverse le média, on crée un créatif, on crée l'annonce qui pointe dessus.
curl -X POST "https://graph-video.facebook.com/v21.0/act_XXX/advideos" \
-F "access_token=$TOKEN" -F "source=@video.mp4" -F "name=mon-etiquette"
Note l'hôte : graph-video, pas graph. Puis attends que la vidéo soit prête avant de l'utiliser, sinon la création du créatif échoue :
GET /{VIDEO_ID}?fields=status
# attendre status.video_status == "ready"
Sur une connexion domestique, tu montes souvent à moins d'un mégaoctet par seconde. Une vidéo de 50 Mo prend deux minutes, cent vidéos prennent la nuit. Compresse systématiquement avant d'envoyer : pour du 9:16 vu sur téléphone, une réduction à 1080 de large en qualité moyenne divise le poids par trois sans différence visible dans le fil.
Et écris un journal au fur et à mesure (une ligne par vidéo envoyée, avec son identifiant) : ça permet de reprendre exactement là où ça s'est arrêté quand la connexion tombe.
Une annonce vidéo exige une vignette. Sans elle, Meta refuse avec « Your ad needs a video thumbnail ». Deux solutions : téléverser ton image et utiliser son empreinte, ou récupérer la vignette que Meta a générée automatiquement.
# la vignette automatique, la plus simple
GET /{VIDEO_ID}/thumbnails?fields=uri,is_preferred
# puis, dans video_data : "image_url": ""
curl -X POST "https://graph.facebook.com/v21.0/act_XXX/adimages" \
-F "access_token=$TOKEN" -F "filename=@visuel.png"
# la réponse contient un "hash" : c'est lui qu'on met dans image_hash
Meta suffixe les noms de fichiers téléversés (mon-visuel_105.png). Si ton script fait le lien entre une annonce et un fichier local par le nom, normalise en retirant ce suffixe. J'ai supprimé trente annonces valides à cause de ça.
# créatif vidéo
POST /act_XXX/adcreatives
name = "..."
object_story_spec = {
"page_id": "PAGE_ID",
"video_data": {
"video_id": "...", "image_url": "",
"message": "le texte principal",
"title": "le titre sous la vidéo",
"link_description": "la description",
"call_to_action": {"type":"LEARN_MORE","value":{"link":"https://..."}}
}
}
url_tags = "utm_source=meta&utm_medium=paid&..."
# annonce
POST /act_XXX/ads
name = "..."
adset_id = "..."
creative = {"creative_id": "..."}
tracking_specs = [{"action.type":["offsite_conversion"],"fb_pixel":["PIXEL_ID"]}]
status = "PAUSED"
On crée tout en PAUSED, on vérifie, et on allume ensuite. Une annonce créée active part immédiatement en diffusion, avant même que tu aies relu le texte.
C'est l'opération la plus utile du guide. Tu veux changer la vidéo d'une annonce qui tourne bien, sans repartir de zéro.
Impossible de modifier un créatif : il est immuable. Mais on peut créer un nouveau créatif et le rattacher à l'annonce existante. L'annonce garde son identifiant, son ensemble, son historique de dépense.
POST /{AD_ID}
creative = {"creative_id": "NOUVEAU_CREATIF"}
Tu gardes : l'annonce, ses statistiques, sa place dans l'ensemble. Tu perds : la preuve sociale (mentions j'aime, commentaires, partages accumulés sur l'ancienne publication) et l'annonce repasse en revue quelques heures. C'est le prix, il est connu, il vaut souvent le coup.
Meta accepte jusqu'à cinq titres, cinq textes et plusieurs descriptions dans un même créatif, et teste les combinaisons. Ça passe par asset_feed_spec au lieu de object_story_spec.
asset_feed_spec = {
"ad_formats": ["SINGLE_VIDEO"],
"videos": [{"video_id":"...", "thumbnail_hash":"..."}],
"titles": [{"text":"titre 1"}, {"text":"titre 2"}, ...],
"bodies": [{"text":"texte 1"}, {"text":"texte 2"}, ...],
"descriptions": [{"text":"..."}],
"link_urls": [{"website_url":"https://..."}],
"call_to_action_types": ["LEARN_MORE"]
}
1. On ne peut pas créer une annonce directement avec ce format dans un ensemble neuf. Il faut créer l'annonce avec un créatif simple, puis lui affecter le créatif multi-variantes.
2. Dès qu'une annonce de l'ensemble porte ce format, l'ensemble entier bascule en mode dynamique et refuse alors toute nouvelle annonce, ainsi que la suppression de ses annonces existantes.
3. Donc : un ensemble dynamique ne contient qu'une seule annonce. Si tu veux dix annonces lisibles, garde un titre et un texte par annonce.
Ces onze erreurs reviennent systématiquement quand on monte des campagnes par l'API. Elles sont listées dans l'ordre où elles se présentent, avec le message que Meta renvoie et la parade.
| Ce que Meta dit | Ce que ça veut dire | La parade |
|---|---|---|
| Cannot disable Advantage options | Tu essaies de désactiver l'audience Advantage+ sur un ensemble existant | Impossible en modification. Recréer l'ensemble avec le réglage, ou le corriger dans l'interface |
| rien du tout, mais l'ensemble est dynamique | Tu as envoyé is_dynamic_creative:"false" | En envoi de formulaire, toute chaîne non vide vaut vrai. Ne pas envoyer le champ |
| Cannot create or update ads in dynamic creative ad set | L'ensemble est passé en mode dynamique | Une seule annonce par ensemble dynamique. Sinon, un seul titre et un seul texte par annonce |
| Dynamic creative ad deletion is not allowed | Tu essaies de supprimer une annonce d'un ensemble dynamique | La mettre en pause et la renommer, ou recréer l'ensemble |
| Cannot create dynamic creative ad in non-dynamic ad set | Création directe d'une annonce multi-variantes | Créer l'annonce simple, puis lui affecter le créatif multi-variantes |
| Your ad needs a video thumbnail | Créatif vidéo sans vignette | Fournir image_hash ou image_url, ou prendre la vignette automatique |
| Tracking pixel required | Campagne à objectif conversions sans pixel déclaré sur l'annonce | Ajouter tracking_specs sur l'annonce, même si l'ensemble porte déjà le pixel |
| blame_field_specs: frequency_control_specs | Plafond de fréquence avec un objectif de conversion | Le plafond n'existe qu'en couverture. Choisir l'un ou l'autre |
| must be a valid Instagram account id | instagram_actor_id dans un link_data | Accepté en vidéo, refusé en lien. L'omettre, Meta prend le compte lié à la Page |
| isn't associated with a Page | Vidéo envoyée par API, utilisée dans une audience de vues | Publier la vidéo via la Page si tu veux retargeter ses spectateurs |
| User request limit reached (code 17) | Trop d'écritures trop vite sur le compte | Quelques secondes entre chaque écriture, relance automatique après deux minutes, script en tâche de fond avec journal |
Après chaque création, relis l'objet que tu viens de créer et compare-le à ce que tu croyais envoyer. Les booléens mal interprétés, les champs ignorés en silence et les réglages activés par défaut ne se voient que comme ça. Un GET après chaque POST coûte une seconde et évite des reconstructions entières.
Par défaut, Meta attribue une conversion à une publicité si la personne a cliqué dans les sept jours ou vu la publicité dans la journée. Ce n'est pas une vérité, c'est une convention. Deux comptes avec des fenêtres différentes ne se comparent pas.
"attribution_spec": [
{"event_type":"CLICK_THROUGH","window_days":7},
{"event_type":"VIEW_THROUGH","window_days":1}
]
Fixe la même partout, note-la, et souviens-toi qu'une conversion vue-mais-pas-cliquée est un signal faible : quelqu'un a vu ta pub et a acheté le lendemain, peut-être à cause d'elle, peut-être pas.
Ton outil interne et Meta ne diront jamais le même nombre, et c'est normal : Meta attribue à l'annonce, ton outil attribue à la source du clic ; Meta compte dans la fenêtre, ton outil compte à la date réelle ; Meta déduplique par identifiant, ton outil par personne.
Meta sert à piloter la diffusion (quelle annonce coupe-t-on, laquelle monte-t-on). Ton outil interne sert à compter l'argent. Ne mélange jamais les deux dans un même tableau sans le dire, et ne demande jamais à l'un de valider l'autre.
Le travail de plomberie ne consiste pas à brancher, il consiste à prouver que c'est branché. Voici les contrôles qui ont réellement attrapé des problèmes.
Prends les cent dernières conversions réelles dans ton système, et regarde combien sont arrivées chez Meta. En dessous de 90 %, tu as un trou quelque part. À 99 %, ta chaîne est saine.
Si tu as plusieurs pixels, vérifie qu'aucun évènement d'un marché n'atterrit dans le pixel de l'autre. Ça arrive dès qu'une page est partagée entre deux parcours.
Demande à ton pixel ses statistiques par URL. Tu y trouveras parfois un ancien domaine, un environnement de test, ou un site que tu ne gères plus. Chacun pollue tes audiences.
Ouvre la règle de chaque audience et lis-la. Les questions qui piquent : est-ce que cette audience de vues contient encore des vidéos d'annonces mortes ? Est-ce que cette audience de visiteurs filtre bien le bon domaine ? Est-ce que mes exclusions sont là ? Est-ce que l'audience est utilisée ou seulement suggérée ?
Le statut d'une campagne ne dit rien de ses annonces. Une campagne peut être active avec tous ses ensembles en pause, et l'inverse existe aussi. Avant d'affirmer que quelque chose tourne ou ne tourne pas, vérifie les trois niveaux et la dépense du jour. C'est le seul chiffre qui ne ment pas.
Écris un petit script qui relit tout le compte et vérifie une liste de règles : audience stricte, exclusions présentes, destination conforme à l'étage, paramètres d'URL sur chaque annonce, absence de doublons, statut attendu, dépense nulle si tout doit être en pause. Lance-le avant chaque mise en ligne. C'est ce contrôle qui a trouvé la seule anomalie restante sur un parc de deux cents annonces.
Rien ici n'est de la théorie de média buying. C'est la plomberie : ce qui relie un clic à un chiffre. Elle n'impressionne personne quand elle marche, et elle fait perdre des mois quand elle fuit. Le seul réflexe qui compte vraiment : ne jamais croire un chiffre qu'on n'a pas vérifié soi-même, à la source.