Skip to content

feat: measure clicks through to the ticket shop - #18

Merged
thevvalverde merged 3 commits into
mainfrom
feat/ticket-link-analytics
Sep 11, 2026
Merged

thevvalverde merged 3 commits into
mainfrom
feat/ticket-link-analytics

Conversation

@thevvalverde

Copy link
Copy Markdown
Collaborator

What

Adds a select_ticket_link GA4 event fired when a visitor follows any link to the Pretix ticket shop, so we can see which pages and which traffic sources actually lead people to tickets.

Why not purchase or begin_checkout

The purchase happens on Pretix and we never learn whether it completed, so a purchase event would be a claim we cannot support. begin_checkout was rejected for the same reason at one remove: it flows into GA4's ecommerce funnel reports and would invent a funnel with a permanently missing bottom. A custom event keeps the reports honest. There is a test pinning this.

To count it as a conversion, mark it as a key event in the GA4 UI — no code change needed.

How

One delegated listener on the document rather than a handler per link. Ticket links appear in the header, the home hero, the four ticket cards, navigation.json and the FAQ Markdown; matching on the shop host means a link added later, including one written in content, is measured with no extra work.

  • PRETIX_HOST is a setting so there is a single thing to change, and so a staging shop still reports.
  • The hit goes out with transport_type: 'beacon', because the click navigates away and a normal request would be cancelled in flight.
  • Traffic source is deliberately not attached: GA4 resolves source/medium/campaign for the session, so the event inherits the attribution of the visit it belongs to. Only source_page and the destination — which GA4 cannot know — are passed.

Privacy

No change to the consent posture. The listener returns early when the tag was never loaded, so a visitor who declines is not measured and no request reaches Google — the promise the privacy guide already makes. The guide is updated to state what is now recorded and its limit.

Counting clicks from visitors who decline was considered and dropped: GA4's Measurement Protocol requires a client_id, and a random one per click manufactures a fake user each time, corrupting user and session counts. A server-side counter was the alternative, but base.py states plainly that the site has no database, and this did not seem worth reversing that for. Consequence worth knowing when reading the numbers: ticket-click counts undercount by roughly the decline rate, so they are sound for comparing channels rather than as an absolute measure of interest.

Testing

  • 23 tests pass (6 new).
  • The new assertions were mutation-checked — swapping the event name for begin_checkout turns two of them red.
  • The consent gate itself was exercised against the real rendered script in a headless DOM: an accepted visitor fires exactly one event, a declined visitor fires nothing and never requests gtag.js, non-ticket links are ignored, a click on an element nested inside the link still resolves, and a lookalike host (evil-pretix.evolutio.pt.attacker.com) is correctly rejected.
  • test_the_home_page_actually_links_to_that_host is the test that earns its keep: the tracker matches on the host, so a ticket link edited to point elsewhere would silently stop being measured — this turns that into a red test.

Follow-up in the GA4 UI (not code)

Custom dimensions are not retroactive, so this is worth doing right after deploy or the parameters read (not set) forever. Under Admin → Custom definitions, register link_url, link_text and source_page as event-scoped dimensions.

🤖 Generated with Claude Code

https://claude.ai/code/session_011i8AYdEkHBURzkFmyGpQmy

thevvalverde and others added 3 commits September 11, 2026 11:18
GA4 reports how people arrive and which pages they read, but nothing
about the step that matters most: leaving for the Pretix shop. Add a
select_ticket_link event so that intent is visible.

The event is custom rather than begin_checkout because the purchase
happens on Pretix and we never learn whether it completed; feeding it
into the ecommerce funnel would invent a funnel we cannot close. Mark it
as a key event in the GA4 UI to count it as a conversion.

One delegated listener rather than a handler per link: ticket links
appear in the header, the home hero, the ticket cards, navigation.json
and the Markdown content, so matching on the shop host means a link
added later is measured with no extra work. The host is a setting so
there is a single thing to change, and so a staging shop still reports.

Traffic source is deliberately not attached. GA4 resolves source, medium
and campaign for the session, so the event already carries the
attribution of the visit it belongs to; only the source page and the
destination, which GA4 cannot know, are passed.

The hit goes out by beacon because the click navigates away and a normal
request would be cancelled in flight.

Nothing is measured without consent: the listener returns early when the
tag was never loaded, so a visitor who declines is still not tracked and
no request reaches Google.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011i8AYdEkHBURzkFmyGpQmy
Pins the parts that are easy to break silently: the tracker ships only
when a measurement ID is configured, it matches the configured shop
host, it sends by beacon, and it stays off the ecommerce funnel events.

The last test is the one that earns its keep. The tracker matches on the
host, so a ticket link edited to point somewhere else would simply stop
being measured with nothing failing; asserting the home page links to
the configured host turns that into a red test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011i8AYdEkHBURzkFmyGpQmy
The privacy guide describes what analytics collects, so it has to
mention the new event. States the limit as well as the fact: we see that
a link was followed, never whether a purchase was made, and a visitor
who declines is not measured at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011i8AYdEkHBURzkFmyGpQmy
@thevvalverde
thevvalverde merged commit 34eb06a into main Sep 11, 2026
3 checks passed
@thevvalverde
thevvalverde deleted the feat/ticket-link-analytics branch September 11, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant