Blog
Eventi e-commerce senza una piattaforma di negozio: come far sì che un checkout su misura riporti a GA4
Pubblicato · 6 min di lettura
Una piattaforma di negozio ti dà almeno un plugin con cui litigare. Un checkout che hai costruito da solo — un’app React, un flusso di prenotazione, una registrazione SaaS con Stripe dietro — non ti dà nulla: nessun evento si attiva finché non è il tuo codice a inviarlo. La buona notizia è che il contratto è piccolo: una manciata di nomi, la forma di un oggetto e poche regole su dove va ogni push. Eccolo tutto, compresa la parte che la maggior parte delle guide tralascia: gli acquisti che avvengono quando sulla tua pagina non c’è nessuno.
Il contratto: un nome e un oggetto ecommerce
Tutto ciò che trovi qui arriva ad Analytics nello stesso modo. La tua pagina invia un evento a window.dataLayer con il suo nome GA4 e un oggetto ecommerce nella forma di GA4. Un attivatore di Tag Manager riconosce il nome, e un tag evento GA4 legge l’oggetto e lo inoltra al tuo container server. Se hai detto a SignalHost che il tuo sito è un negozio — o hai premuto «Configura» nella sezione «Eventi di conversione» della pagina del container — quegli attivatori e tag esistono già nel tuo container web, con il prefisso SH nel nome. Ciò che resta è il push.
Due regole intercettano la maggior parte degli errori. Svuota prima l’oggetto ecommerce precedente, altrimenti un view_item di tre pagine fa si infila nell’evento successivo. E in ogni evento che porta denaro, invia value come numero e currency come codice ISO: un value in forma di stringa o l’assenza di currency vengono accettati senza obiezioni e restano esclusi dalle entrate in silenzio.
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: 'begin_checkout',
ecommerce: {
currency: 'EUR',
value: 49.00,
items: [
{ item_id: 'plan-pro', item_name: 'Pro', price: 49.00, quantity: 1 }
]
}
});Quale evento, e dove va
Un flusso su misura ha gli stessi momenti di una piattaforma di negozio. Devi solo trovarli nel tuo codice.
- view_item_list: una pagina o un componente che mostra più prodotti o piani. Una volta, quando l’elenco viene mostrato, non a ogni scorrimento.
- view_item: la pagina di un singolo prodotto o piano.
- add_to_cart e remove_from_cart: nell’handler del pulsante, dopo che il tuo carrello è davvero cambiato.
- begin_checkout: il momento in cui il visitatore entra nel checkout, cioè il primo rendering della pagina di checkout o il clic che apre una finestra di checkout.
- add_payment_info: quando i dati di pagamento vengono accettati, non quando compare il modulo.
- purchase: una volta, quando l’ordine è confermato. Gran parte di questo articolo serve a gestire bene proprio questo.
- sign_up e start_trial: per un’attività in abbonamento, le conversioni che precedono qualsiasi pagamento. SignalHost costruisce i tag per entrambi insieme agli eventi del negozio.
Invia dall’handler che ha causato il cambiamento, non da un render. Una single-page app si ri-renderizza liberamente, e React esegue gli effetti due volte in sviluppo: un push dentro un effetto raddoppia nella build di sviluppo e si ripete ogni volta che un componente viene rimontato.
L’acquisto nella tua pagina di conferma: leggi l’ordine dal tuo server
Quando il visitatore torna su una tua pagina dopo aver pagato, è quella la pagina in cui va purchase, a due condizioni. Prendi l’ordine dal tuo backend, non dall’URL: i parametri di query si possono modificare, vengono eliminati dai redirect e arrivano anche con i pagamenti non riusciti. Stripe, per dirne uno, rimanda allo stesso indirizzo sia che il pagamento sia andato a buon fine sia che no, e lo indica in un parametro tutto suo. E invialo una sola volta: un ricaricamento, il pulsante Indietro o una pagina di ricevuta salvata nei preferiti riportano tutti lì.
// On your confirmation page. The order comes from YOUR server, by an id the
// page already knows — never from price or status parameters in the URL.
const order = await fetch(`/api/orders/${orderId}`).then((r) => r.json());
const key = `purchase-sent-${order.id}`;
if (order.status === 'paid' && !localStorage.getItem(key)) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: order.id,
value: order.total,
tax: order.tax,
currency: order.currency,
items: order.items.map((i) => ({
item_id: i.sku, item_name: i.name, price: i.price, quantity: i.quantity
}))
}
});
localStorage.setItem(key, '1');
}transaction_id è una rete di sicurezza, non una strategia. Analytics scarta un transaction_id ripetuto all’interno di una sessione, ma non un ricaricamento della pagina di ricevuta la mattina dopo.
Il pagamento sulla pagina di qualcun altro: Stripe Checkout, PayPal, un redirect verso la banca
Una pagina di pagamento ospitata è la scelta abituale per un progetto su misura, e ha un punto critico: il visitatore potrebbe non tornare mai. Paga, chiude la scheda e la tua pagina di conferma non viene mai renderizzata. Con le carte si tratta di pochi punti percentuali degli acquisti. Con bonifici, fatture e metodi di pagamento posticipato può trattarsi della maggior parte, perché il denaro arriva ore o giorni dopo.
Quindi decidi, per ogni metodo di pagamento, dove sta la verità. Se il tuo backend viene a sapere del pagamento da un webhook, il webhook è il posto affidabile da cui riportarlo — vedi la sezione successiva — e la pagina di conferma non deve inviare anche purchase, altrimenti ogni pagamento con carta viene contato due volte. Se l’unico posto in cui lo vieni a sapere è la pagina di ritorno, riportalo lì e accetta la lacuna.
Gli acquisti a cui nessun browser assiste: inviali dal tuo server
Con gli abbonamenti è inevitabile. Una prova si converte dopo sette giorni, un rinnovo arriva ogni mese, una fattura viene pagata con bonifico la settimana prossima. Ognuno è un acquisto, e nessuno avviene davanti a un browser. La soluzione è inviare l’evento da server a server, dal webhook che viene a sapere del pagamento, allo stesso endpoint di tagging usato dalle tue pagine — il tuo nome host SignalHost o il tuo dominio — così che passi dal tuo container server come ogni altro hit.
Dal checkout serve una sola cosa: il client id Analytics del visitatore, così che l’acquisto venga attribuito alla persona le cui visite lo hanno generato. Si trova nel cookie _ga, che la tua richiesta di checkout porta già con sé quando va al tuo dominio. Salvalo insieme all’ordine. Nessun cookie _ga significa che il visitatore non ha dato il consenso all’analisi: invia l’hit come consenso negato con un id usa e getta, oppure non inviarlo affatto — non inventargli mai un’identità. Se inoltri le conversioni anche a Meta, TikTok o Google Ads, salva insieme all’ordine la scelta del visitatore sulla pubblicità e invia G111 se l’ha accettata: un acquisto marcato G101 arriva ad Analytics ma non a Meta né a TikTok, e Google Ads non può collegarlo a un clic.
// In your checkout request handler: keep the visitor's Analytics id with the order.
const ga = req.cookies._ga; // "GA1.1.1234567890.1700000000"
order.gaClientId = ga ? ga.split('.').slice(-2).join('.') : null;
// Later, in your payment webhook — a trial converting, an invoice paid:
const params = new URLSearchParams({
v: '2',
tid: 'G-XXXXXXXXXX', // your GA4 measurement ID
cid: order.gaClientId ?? `${Date.now()}.${Math.floor(Math.random() * 1e9)}`,
gcs: order.gaClientId ? 'G101' : 'G100', // no _ga cookie = no consent
en: 'purchase',
'ep.transaction_id': invoice.id,
'epn.value': '49.00',
cu: 'EUR',
pr1: 'idplan-pro~nmPro~pr49.00~qt1',
});
await fetch(`https://metrics.example.com/g/collect?${params}`, { method: 'POST' });Scegli una sola fonte per evento e attieniti a quella. È esattamente così che signalhost.io misura le proprie vendite: il browser invia sign_up, begin_checkout e start_trial; il webhook di Stripe invia purchase quando viene pagata la prima fattura; nessun evento parte da entrambi. Anche il Measurement Protocol di Google funzionerebbe, ma va direttamente a Google e aggira il tuo container server, quindi nessuno dei tuoi tag lato server vede mai la vendita.
Gli acquisti tardivi sono uno dei quattro motivi per cui GA4 conta meno del tuo elenco ordini: gli altri tre, e quali puoi correggere.
Il consenso non ti chiede nulla in più
Invia gli eventi qualunque cosa abbia scelto il visitatore. I tag obbediscono a Consent Mode: con il consenso inviano normalmente; senza, inviano ping senza cookie che Analytics modella anziché riportare. Trattenere tu stesso i push nasconde soltanto conversioni a quella modellazione. Ciò che conta davvero è l’ordine: lo snippet di Tag Manager va dopo lo script del tuo banner del consenso, così i valori predefiniti del consenso esistono prima che venga inviato il primo evento.
Verificare che funzioni
- Apri il container in SignalHost. Il riquadro degli eventi elenca ciò che è arrivato, per nome, con la quota che portava il consenso. Un nome che hai inviato e che lì non vedi non ha mai raggiunto il tuo server di tagging.
- Percorri il tuo checkout nella modalità Anteprima di Tag Manager. Mostra ogni push, quale tag si è attivato e l’oggetto ecommerce che è stato inviato.
- In Analytics, DebugView mostra gli eventi man mano che arrivano. I report standard possono richiedere un giorno.
- Ricarica due volte la pagina di conferma e conta gli acquisti. Dovrebbe essercene ancora uno.
Qui i guasti sono tutti silenziosi: un nome con le maiuscole sbagliate, value inviato come stringa, items annidato un livello troppo in profondità. Nessuno di essi produce un errore, da nessuna parte. Ognuno produce un report con un buco.
