โšก Swarm Architecture

GetSite per-button tracking via GTM + GA4

# 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 `

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.