# GetSite per-button tracking via GTM + GA4
Backlog reference: #10 "GetSite widge tagging โ Currently multiple buttons on GetSite pop-ups are not trackable individually, need to find out a way to track"
Owner: Darshan (GTM-side) + whoever edits the GetSite widget HTML
Status: Spec โ no API needed (the API path is dormant because Lyreco doesn't have GetSiteControl API access). This is the path that doesn't need any vendor cooperation.
---
The shape of the fix
GetSite renders a popup as plain HTML inside the page. Each button is a clickable element. We attach a data-* attribute to each button identifying what it is, and a small JavaScript handler that pushes a structured event onto the GTM dataLayer. GTM then has a Custom Event trigger that catches those pushes and forwards them to GA4 as a custom event. The event lands in our ga4_action_events table the same way every other tracked action does.
`
GetSite widget HTML โ dataLayer.push โ GTM Custom Event โ GA4 event
(data-attrs on buttons) (in browser) trigger (โ ga4_action_events)
`
---
Step 1 โ GetSite widget HTML (the snippet)
Inside each GetSite widget's HTML editor, tag every button with data-gsc-button-id + data-gsc-button-label, and add one global block at the bottom that wires the click handler. Once per widget, not per button.
`html
Talk to us
`
Convention for data-gsc-button-id: lowercase, underscores, stable across widget edits. Examples: cta_primary, dismiss, learn_more, submit_form, option_a, option_b. Keep them โค 32 chars. Don't change them once data starts flowing or you'll fragment the analytics.
---
Step 2 โ GTM trigger + tag (Darshan in the Lyreco GTM container)
Custom Event trigger
| Field | Value |
|---|---|
| Trigger type | Custom Event |
| Event name | getsite_button_click |
| This trigger fires on | All Custom Events |
Data Layer Variables (one per field we want in GA4)
| Variable name (GTM) | Data Layer Variable Name |
|---|---|
| DLV - widget_id | widget_id |
| DLV - button_id | button_id |
| DLV - button_label | button_label |
| DLV - page_path | page_path |
GA4 Event Tag
| Field | Value |
|---|---|
| Tag type | Google Analytics: GA4 Event |
| Configuration tag | (existing Lyreco GA4 config tag) |
| Event Name | getsite_button_click |
| Event Parameters | widget_id โ {{DLV - widget_id}}
button_id โ {{DLV - button_id}}
button_label โ {{DLV - button_label}}
page_path โ {{DLV - page_path}} |
| Trigger | the Custom Event trigger above |
---
Step 3 โ Register custom dimensions in GA4 (one-time)
In GA4 โ Admin โ Custom Definitions โ Custom Dimensions, create:
| Dimension name | Scope | Event parameter | Description |
|---|---|---|---|
| widget_id | Event | widget_id | GetSite widget where the click happened |
| button_id | Event | button_id | Specific button tracked |
| button_label | Event | button_label | Human-readable label at click time |
After ~24h GA4 starts surfacing these in Explore reports.
---
Step 4 โ How it lands in our database
The event flows through our existing GA4 โ Oracle โ TimescaleDB pipeline (workers/oracle_ga4_action_pull.py). The getsite_button_click event will land in ga4_action_events with the event-parameter JSON columns containing widget_id, button_id, button_label. To query per-button click counts:
`sql
SELECT
event_params->>'widget_id' AS widget,
event_params->>'button_id' AS button,
event_params->>'button_label' AS label,
source_country,
COUNT(*) AS clicks,
COUNT(DISTINCT user_pseudo_id) AS distinct_visitors
FROM ga4_action_events
WHERE event_name = 'getsite_button_click'
AND event_date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY 1, 2, 3, 4
ORDER BY clicks DESC;
`
(Adjust event_params column name to the actual JSONB-or-flattened column shape in our table โ check the schema before shipping the dashboard query.)
---
Implementation order
1. Darshan sets up the GTM trigger + tag + data layer variables (Step 2) and the GA4 custom dimensions (Step 3). Publishes a draft container to GTM Preview mode.
2. Widget HTML edits go through Lyreco's batch-deployment flow โ the snippet from Step 1 cannot be pasted directly into the GetSite admin in this org (per Pierre, deploys are batch-file driven). Provide the snippet as a deployable artifact for whichever build/deploy chain manages the widget assets.
3. Pilot ONE widget end-to-end first: confirm the getsite_button_click event fires in GTM Preview before scaling out.
4. After 24h verify the event shows up in GA4 โ Reports โ Engagement โ Events. Custom dimensions populate.
5. After 48h verify the event shows up in our ga4_action_events table (it'll arrive via the existing workers/oracle_ga4_action_pull.py pipeline โ no new worker needed, no scheduling change).
6. Roll out the snippet to every other GetSite widget. Use a consistent naming convention so cross-widget comparisons work.
---
Why this beats the API path
| Aspect | GTM/dataLayer (this) | GetSite API |
|---|---|---|
| Vendor dep | None | Needs API tier + key + docs |
| Setup | GTM config + widget HTML edits | Same widget edits + API client + sync worker + schema |
| Data freshness | Same as GA4 (~hourly via our Oracle pipeline) | Whenever the sync worker runs |
| Lives where | ga4_action_events (existing infra) | New getsite_events table (new infra) |
| Joins with GA4 sessions | Native (same user_pseudo_id) | Needs visitor_id bridge |
| Cost to maintain | One snippet per widget, GTM config | Worker + watermark + endpoint changes when GetSite updates API |
This is also forward-compatible: if Lyreco later gets GetSite API access, the API integration becomes additive (engagement aggregates from GetSite-side) โ it doesn't replace this granular per-button tracking. The two layers coexist cleanly.