A/B testing de prix au niveau landing
price_overridesest un champ JSONB surlanding_pagesqui mappenative_product_variant.id → prix override. Le checkout natif utilise ce prix au lieu duvariant.pricepour les visiteurs qui ont vu cette landing. Combiné au flow weighted, c'est le pattern "4 tiers de prix simultanés".
Concept
Pour tester si MemoMind se vend mieux à $24.99 ou $44.99, l'approche classique force à :
- Créer 4 produits ou 4 variants (un par prix) → casse le reporting unifié.
- OU changer le prix manuellement et split les périodes → biaisé par la saisonnalité.
Trackily prend une autre route : un seul produit + un seul variant + N landings qui override le prix au render. Le checkout détecte la landing source, applique le bon override, et la conversion arrive dans la même métrique de produit (juste avec un effective_price différent capturé sur l'order).
Schema price_overrides
{
"id": 31,
"name": "MemoMind — $24.99 tier",
"slug": "memomind-2499",
"type": "ai",
"price_overrides": {
"42": 24.99, // native_product_variants.id 42 → $24.99
"43": 24.99 // si plusieurs variants partagent ce tier
}
}
La clé est l'ID du native_product_variants row (en string JSON). La valeur est le prix override en dollars (float). Pour clear, passe {}.
Comment faire — via MCP
Tool : update_landing.
{
"name": "update_landing",
"arguments": {
"id": 31,
"price_overrides": { "42": 24.99 }
}
}
Trackily écrit la map en JSONB sur la landing. Le checkout natif lit ces overrides au moment de pricer le cart (/native/checkout/pricing).
Pour effacer :
{
"name": "update_landing",
"arguments": { "id": 31, "price_overrides": {} }
}
Le setup MemoMind (campaign #8 — live sur Trackily)
Sur l'instance prod trackily.online, voici le setup A/B price test live :
Step 1 — Un seul produit natif
native_productsrow : "MemoMind 30-day pack".native_product_variantsrow : id42, price$34.99(base price, le "ne sera jamais affiché").
Step 2 — 4 landings, chacune avec son override
{ "id": 31, "slug": "memomind-tier-1", "price_overrides": { "42": 24.99 } }
{ "id": 32, "slug": "memomind-tier-2", "price_overrides": { "42": 34.99 } }
{ "id": 33, "slug": "memomind-tier-3", "price_overrides": { "42": 44.99 } }
{ "id": 34, "slug": "memomind-tier-4", "price_overrides": { "42": 54.99 } }
Les 4 landings sont générées avec le MÊME HTML (archetype wellness, framework pas, image provider openai_gpt_image). Seul le prix affiché dans le bloc pricing.single_box est différent — il est interpolé depuis price_overrides au render.
Step 3 — Flow 25/25/25/25
{
"name": "create_flow",
"arguments": {
"campaign_id": 8,
"type": "default",
"binding": "cookie",
"binding_hours": 168,
"schema": {
"landings": [
{ "id": 31, "weight": 25 },
{ "id": 32, "weight": 25 },
{ "id": 33, "weight": 25 },
{ "id": 34, "weight": 25 }
],
"offers": []
}
}
}
binding: cookie + 168h : le visiteur voit TOUJOURS la même tier au cours d'une fenêtre 7 jours — sinon il pourrait voir $24.99 le lundi puis $54.99 le mercredi en revenant via retargeting, ce qui ruine l'A/B et la confiance.
offers: [] car le checkout est natif (pas un postback affiliate). C'est l'order native qui capture la conversion + l'effective_price en fonction de la landing source.
Step 4 — Métrique decisive : revenue per 1000 clicks (RPM)
Tu ne mesures PAS la conversion rate par tier — elle décroît mécaniquement avec le prix. Tu mesures revenue par 1000 clics :
| Tier | Prix | CR estimée | RPM (CR × prix × 10) |
|---|---|---|---|
| 1 | $24.99 | 3.5 % | $874 |
| 2 | $34.99 | 2.4 % | $840 |
| 3 | $44.99 | 1.8 % | $810 |
| 4 | $54.99 | 1.3 % | $715 |
(Chiffres illustratifs.) Le winner est le tier qui maximise RPM, pas le tier au CR le plus haut.
Step 5 — Kill switch automizer
{
"name": "create_automizer_rule",
"arguments": {
"name": "Kill MemoMind tier 4 si RPM < $500 après 1000 clics",
"campaign_id": 8,
"conditions": [
{ "metric": "clicks", "operator": ">=", "value": 1000, "window": "7d" },
{ "metric": "revenue", "operator": "<", "value": 500, "window": "7d" }
],
"actions": [{ "type": "pause_landing", "landing_id": 34 }],
"check_interval_minutes": 60
}
}
(Comme la condition mesure la campagne entière, en production tu raffines avec une condition par-landing — voir automizer/conditions.)
Variations du pattern
A/B simple : 2 prix
{
"schema": {
"landings": [
{ "id": 31, "weight": 50 },
{ "id": 32, "weight": 50 }
]
}
}
3 tiers (low / mid / premium)
{
"schema": {
"landings": [
{ "id": 31, "weight": 33 },
{ "id": 32, "weight": 34 },
{ "id": 33, "weight": 33 }
]
}
}
Override discount targeted geo (forced flow)
Si tu veux que la landing #31 (prix bas) ne soit servie qu'aux pays low-income :
{
"name": "create_flow",
"arguments": {
"campaign_id": 8,
"type": "forced",
"name": "Low-income geo cheaper",
"filters": [{ "key": "country", "op": "in", "value": ["IN", "PH", "VN", "BR"] }],
"schema": { "landings": [{ "id": 31, "weight": 100 }] }
}
}
Puis un default qui sert les autres tiers aux pays high-income.
Différences avec un A/B test de landing classique
| Aspect | A/B landing classique | A/B price overrides |
|---|---|---|
| HTML | Différent par variante | Identique |
| Variable testée | Copy, design, structure | Prix uniquement |
| Bias possible | Plus de variables = bruit | Une variable seulement = clean |
| Reporting | Conv rate par landing | RPM par landing |
| Setup time | Long (créer N landings) | Très court (un genai + N updates) |
Le price overrides est strictement plus propre quand tu veux isoler la sensibilité prix.
Génération efficace des N landings
Tu peux utiliser generate_landing_variants pour cloner la landing originale 3 fois, puis update chaque variante avec son override de prix :
{ "name": "generate_landing_variants", "arguments": { "ai_landing_id": 31, "count": 3 } }
Réponse retourne [32, 33, 34] (les ids des nouveaux siblings). Puis :
{ "name": "update_landing", "arguments": { "id": 32, "price_overrides": { "42": 34.99 } } }
{ "name": "update_landing", "arguments": { "id": 33, "price_overrides": { "42": 44.99 } } }
{ "name": "update_landing", "arguments": { "id": 34, "price_overrides": { "42": 54.99 } } }
Toutes les variantes partagent le variant_group_id — bonus pour le reporting.
Le checkout natif et effective_price
Quand le visiteur passe au checkout depuis landing #31 (/native/checkout?landing_id=31), Trackily :
- Lit
landing_pages.price_overridespourid=31. - Pour chaque variant dans le cart, override le prix si la clé existe dans la map.
- Stocke
orders.effective_unit_price(snapshot — l'order garde le prix même si tu changes les overrides plus tard). - Inscrit
orders.source_landing_id = 31pour pouvoir grouper par landing dans les reports.
Le report "Revenue by landing" est ensuite trivial : GROUP BY source_landing_id puis SUM(effective_unit_price * quantity).
Erreurs courantes
- Override sur un variant.id qui n'existe pas : silent, l'override est ignoré (le prix base s'applique). Vérifie les IDs.
- Clé en number vs string :
{ 42: 24.99 }vs{ "42": 24.99 }. JSONB normalise en string — toujours utiliser des string keys côté JS. - Binding off : sans
binding: cookie, le visiteur revient sur une tier différente, voit un autre prix, se sent floué. Toujours cookie sur du price testing. - Override > price base : si tu mets $74.99 quand le base est $34.99, OK c'est un upsell — mais vérifie que c'est intentional.
- Mixer price_overrides + Stripe Coupon : double discount possible. Choisis un mécanisme à la fois.
- Pas de revenue tracking : si la landing ne route pas vers un produit natif (
type: redirect_trackervers offer affiliate), lesprice_overridesne servent à rien — le revenu vient du postback affiliate qui ignore les overrides. - Pas assez de volume par tier : 100 clics × 4 tiers = 25 clics chacun. Statistiquement nul. Attends 500-1000 clics par tier minimum.
Voir aussi
- Flows — rotation pondérée 25/25/25/25
- A/B testing campagnes — la version A/B "vraies landings différentes"
- AI Builder —
generate_landing_variantspour cloner - Types —
type='ai'qui supporteprice_overrides - Commerce — comment le checkout natif consomme les overrides
- Automizer scopé campagne — kill switch par tier