Lesson 213: Privacy-Safe Telemetry Session Receipt Without PII (2026)
Direct answer: Wire game_session_started (or your first session event) with properties build_label and surface only—then file telemetry_session_receipt_v1.json on BUILD_RECEIPT so facilitators can prove which depot players ran without collecting PII. Continues Lesson 212 save slot labels; pairs the PostHog beginner pipeline (evening wire-up) with live-ops receipt discipline.

Why this matters now (July 2026 privacy audits)
July 2026 teams enable analytics the same week they promote fest_public builds. GDPR second-wave audits and Google Play Data Safety reviews ask what you collect—not whether PostHog is “anonymous enough” by marketing copy. Common failures:
- Steam ID in event properties — links playtest and fest sessions to accounts (PII when joined).
- IP retention — PostHog geolocation on by default (
$ip: nullrequired). - No
surfacedimension — dashboards cannot separate playtest_invite from fest_public after Lesson 211 isolation.
This lesson is the course milestone for telemetry_session_receipt_v1.json—not a repeat of the ninety-minute blog tutorial.
Beginner path (35-minute receipt pass)
| Step | Action | Success check |
|---|---|---|
| 1 | Copy VERSION → build_label property |
Matches BUILD_RECEIPT row |
| 2 | Add surface enum |
playtest_invite | fest_public | gx_public |
| 3 | Fire game_session_started once |
Live Events in PostHog |
| 4 | Confirm no PII properties | Schema audit GREEN |
| 5 | Write release-evidence/privacy.md one-pager |
Linked from About |
| 6 | File telemetry_session_receipt_v1.json |
T1–T6 pass |
Time: ~35 minutes after PostHog project exists; 50 minutes first self-hosted vs cloud decision.
Developer path (gates T1–T6)
| Gate | Check | Fail when |
|---|---|---|
| T1 | build_label on every session event |
Missing or hand-typed per build |
| T2 | surface matches playtest scope map |
fest events tagged playtest_invite |
| T3 | PII denylist enforced | steam_id, email, ip, player_name present |
| T4 | $ip: null on capture payload |
IP geolocation enabled |
| T5 | Opt-in / opt-out documented | No functional toggle when EU skew |
| T6 | telemetry_session_receipt_v1.json |
promotion_allowed: false |
Allowed session event schema
Event name: game_session_started (or one agreed name—do not fork per engineer).
Allowed properties:
| Property | Type | Example | Notes |
|---|---|---|---|
build_label |
string | fest-demo-2026-10-rc1 |
From repo VERSION |
surface |
string | fest_public |
Matches facilitator contract |
session_index |
int | 3 |
Install-local counter only |
Forbidden on session events: steam_id, discord_id, email, player_name, country_code from IP, hardware_id, free-text chat.
Godot 4.5 capture snippet (reference)
func _emit_session_started(surface: String) -> void:
Telemetry.capture("game_session_started", {
"build_label": ProjectSettings.get_setting("application/config/version"),
"surface": surface,
"session_index": _session_index
}, ip_null := true)
Unity: same properties on TelemetryClient.Capture; set PostHog $ip null per blog Recipe A.
telemetry_session_receipt_v1.json
{
"schema": "telemetry_session_receipt_v1",
"build_label": "fest-demo-2026-10-rc1",
"analytics_backend": "posthog_cloud_eu",
"session_event_name": "game_session_started",
"allowed_properties": ["build_label", "surface", "session_index"],
"pii_denylist_enforced": true,
"ip_collection_disabled": true,
"opt_in_pattern": "region_aware_default",
"privacy_posture_path": "release-evidence/privacy.md",
"paired_receipts": {
"save_slot_label": "release-evidence/saves/SAVE_SLOT_LABEL_RECEIPT.json",
"facilitator_contract": "release-evidence/facilitator/FACILITATOR_CONTRACT_RECEIPT.json"
},
"gates": {
"T1_build_label_on_session": "pass",
"T2_surface_dimension": "pass",
"T3_pii_denylist": "pass",
"T4_ip_null": "pass",
"T5_opt_in_documented": "pass",
"T6_receipt": "pass"
},
"live_events_proof_capture_id": "posthog-live-2026-05-25-abc",
"promotion_allowed": true
}
Pin under release-evidence/telemetry/TELEMETRY_SESSION_RECEIPT.json.
BUILD_RECEIPT columns
| Column | Value |
|---|---|
telemetry_session_receipt |
Path + pass/fail |
build_label |
Matches upload logs |
surface |
Primary production surface |
save_slot_label_receipt |
Lesson 212 when saves ship |
Thursday row review — add Telemetry row: denylist + Live Events screenshot ID.
Proof table (session audit)
| surface | build_label | Live Events seen? | PII scan | Ship? |
|---|---|---|---|---|
| playtest_invite | playtest-2026-07-rc1 | yes | pass | soak only |
| fest_public | fest-demo-2026-10-rc1 | yes | pass | after T6 |
Key takeaways
build_label+surfaceare the minimum viable session dimensions for multi-channel fest ops.- Receipt proves schema, not that you “use PostHog”—Live Events ID is evidence.
- Denylist is code-enforced—review event JSON in CI, not honor system.
- Lesson 212 saves and Lesson 213 telemetry are independent trust lanes—both belong on BUILD_RECEIPT.
- Crash correlation (214) and refund dashboard (215) assume T1–T2 are already GREEN.
- Pair playtest isolation runbooks for scope vocabulary.
- AI dialogue moderation (216) must not log player prompts in telemetry—keep NPC patches out of analytics.
- Blog pipeline teaches wire-up; this lesson teaches promotion gate.
Prerequisites
- Lesson 212 — save slot label receipt
- Privacy-safe PostHog beginner pipeline
- Lesson 206 — facilitator contract (
surfacevocabulary) - BUILD_RECEIPT beginner pipeline
Common mistakes
- Logging
steam_id“for debugging.” - Using distinct_id = Steam account hash.
- Skipping
surfacebecause “we only have one build.” - Shipping telemetry before
privacy.mdexists. - Mixing chat moderation logs into PostHog.
Troubleshooting
| Symptom | Lane |
|---|---|
Events missing build_label |
T1 + VERSION spine |
| Playtest traffic in fest funnel | T2 + scope map |
| GDPR question from publisher | Blog opt-in patterns + privacy.md |
| Wrong depot in reviews | Lesson 212 + 206, not telemetry alone |
Mini exercise (50 minutes)
- Add denylist unit test rejecting
steam_idproperty. - Emit session on playtest build; verify
surfacein Live Events. - Repeat on fest build; distinct
build_label. - File receipt; screenshot Live Events UUID into
live_events_proof_capture_id. - Update BUILD_RECEIPT row before Wednesday smoke.
Next: Lesson 214 — crash symbolicate + build_label correlation.
Continuity — H1 2026 arc (212–217)
| Lesson | Receipt focus |
|---|---|
| 212 | Save slot labels |
| 213 (this) | Telemetry session without PII |
| 214 | Crash + build_label |
| 215 | Refund signals |
| 216 | AI dialogue patch |
| 217 | H1 capstone |
FAQ
Replace the PostHog blog tutorial?
No—blog is wire-up; this lesson is BUILD_RECEIPT gate for fest promotion.
Can we add level_id later?
Yes—extend allowlist in receipt semver bump; session event stays minimal until T6 passes.
Self-hosted PostHog?
Set analytics_backend accordingly; T3–T4 rules unchanged.
Web HTML5 itch build?
Same build_label + surface; use anonymous distinct_id per blog—never itch username. For Construct GA4 funnel events, pair playtest analytics preflight with Lesson 241 construct playtest analytics receipt.
Prove session telemetry with build_label and surface only—or pause fest analytics until privacy auditors see the receipt.