Piano Analytics
Verify analytics tracking server-side — compare what the browser fires with what Piano actually ingests.
The Piano Analytics integration adds a third source of truth to VisualQ's tracking-test pillar. After the browser-side audit verifies the dataLayer, VisualQ asynchronously queries Piano's Data API and produces a 3-way comparison: plan ↔ browser ↔ Piano.
This catches a category of issues nothing else surfaces:
- Hits dropped by ad-blockers or browser tracking-protection
- Events lost to client-side ITP / sampling
- Wrong site_id on a staging/prod environment swap
- Schema-validation rejections in Piano's ingestion pipeline
- Variables that look correct in the browser but never reach the server
Prerequisites
- A Piano Analytics account (S3 or S4 region)
- An API access key + secret with
data:readscope - The site ID of the Piano site you want to verify against
Setup
1. Generate Piano API credentials
- In Piano, open User profile › API keys
- Click Create new key
- Grant the
data:readscope (Data Query API) - Copy both the Access key and the Secret key
2. Configure in VisualQ
- Open Settings › Integrations on a project
- Click Connect on the Piano Analytics card
- Enter:
- Site ID — the numeric Piano site identifier
- Access key + Secret key — from step 1
- Region —
S3(default) orS4(EU instance)
- Click Save
3. Test the credentials
Re-open the integration and click Test. VisualQ will fire a minimal
getData request to confirm the keys can read your site. The test does
not consume any quota beyond a single API call.
Secrets are encrypted at rest and never returned to the browser. The Settings UI shows masked values (
••••••••) on subsequent loads.
How it works
flowchart LR
A[Run tracking-test] --> B[Worker captures<br/>browser dataLayer<br/>+ Piano cookies]
B --> C[/api/worker/callback]
C --> D[Save audit doc<br/>pianoSummary: pending]
C --> E[Schedule Inngest<br/>tracking/piano.verify]
E -.->|10 min sleep| F[Query Piano Data API]
F --> G{Hits found via<br/>visitor_id?}
G -->|Yes — high confidence| H[Merge 3-way verdict]
G -->|No — fallback| I[Query by URL +<br/>time window]
I -->|Low confidence| H
H --> J[Persist + SSE update UI]Phase 1 — Browser-side audit (synchronous)
When you run a tracking-test, the worker captures the dataLayer and
Piano visitor ID cookies (_pcid, atuserid, idrxvr, …) from the
browser context after each navigation. These cookies, plus the run start
and end timestamps, are sent back to VisualQ along with the usual audit
report.
Phase 2 — Piano-side verification (asynchronous, ~10 min later)
Piano's Data API has a 5–30 minute ingestion latency. VisualQ schedules an Inngest job that:
- Sleeps 10 minutes (covers ~95% of cases)
- Queries Piano's
getDataendpoint withfilter: { user_id: { $in: [<captured visitor IDs>] } }— high-confidence matching - If no rows come back, falls back to
filter: { page_url: { $in: [<URLs>] } }over the run's time window — low-confidence matching (may include real-user traffic) - For every plan variable, resolves the corresponding Piano column
(
analyticsDimensionx4,analyticsMappingpage_chapter1, or the raw variable name) and produces a 3-way verdict
The 3-way verdict matrix
| Browser | Piano | Verdict |
|---|---|---|
| Value matches | Same value | pass — fully verified end-to-end |
| Value matches | Different value | fail — Piano received a different value |
| Value matches | Absent | missing — hit lost in transit (ad-blocker, ITP, …) |
| Absent (mandatory) | Absent | fail — both sides missing a required variable |
| Absent (optional) | Absent | pass — correctly absent on both sides |
not_set | Has value | fail — Piano received an unexpected value |
not_set | Absent | pass — confirmed absent server-side |
What you see in the UI
The tracking audit report grows two extra elements when Piano is enabled:
- A Piano Analytics section in the audit summary card (status: pending → completed/failed) with pass/fail/missing tallies
- A Piano column on every variable row, showing the actual value Piano received and a status icon (✓, ✗, ⚠ missing, ⏱ pending)
Low-confidence banner
When VisualQ fell back to URL + time-window matching (no Piano cookies were captured — typically because the test browser blocked them or your site doesn't load the Piano SDK), an amber banner is shown:
Low-confidence matching Piano hits were matched by URL + time window because no Piano visitor IDs were captured. Results may include real-user traffic during the test window.
To get high-confidence matching, make sure your test environment runs the Piano SDK and accepts the consent banner (or runs in a consent-given state).
Zero configuration on the Piano side
Unlike most analytics QA tools, VisualQ requires no changes to your Piano configuration. We don't ask you to create a custom property, mark hits with a synthetic flag, or expose a separate site for testing. The hybrid identification strategy (visitor cookies → URL + time window) is designed to work against your existing production or staging setup as-is.
Privacy & quota
- Secrets are encrypted at rest in Firestore using the same envelope-encryption scheme as Jira/GitLab tokens
- API calls per run = 1 to 2 (primary + optional fallback)
- No PII is exfiltrated; Piano data flows from Piano → VisualQ only, never the other way
- Quota usage is minimal — Piano's
getDataquota is per-call, and each tracking-test costs at most 2 calls
Troubleshooting
"Piano verification pending" never resolves
- Confirm the integration is enabled (
Settings › Integrations) - Check the Inngest dashboard for the
tracking-piano-verifyfunction; it should fire ~10 minutes after the run completes - Verify the
WORKER_SECRETand Piano credentials are valid (run Test)
Every variable is "missing"
- The site you queried is probably not the one your test hit. Check the Site ID and your environment URLs.
- Your site may not load the Piano SDK in the test environment (e.g. consent not granted). Either accept consent in your scenarios, or expect low-confidence (URL + time window) matching.
Low-confidence banner on every run
- Confirm the Piano SDK loads on the tested URLs
- Check that the Playwright worker isn't blocking third-party cookies
- Cookies VisualQ looks for:
_pcid,atuserid,idrxvr,atid,atauthority
Differences with browser-side tracking QA
| Aspect | Browser-side audit | Piano integration |
|---|---|---|
| What's verified | dataLayer / window vars | Hits ingested by Piano |
| Latency | Synchronous | ~10 minutes (Piano ingestion) |
| Catches ad-blocker drops? | No | Yes |
| Catches schema rejection? | No | Yes |
| Catches wrong site_id? | No | Yes |
| Catches dataLayer typos? | Yes | Partially |
The two layers are complementary — keep both enabled for the strongest guarantees.