Before You Start: Confirm What Should Happen
Paywall behavior depends on three things working together:
-
Experience configuration — Composer targeting and priority
-
Identity + entitlements — Is the user recognized, and do they have access?
-
Rendering — Is the template/container visible and not blocked by CSS or JavaScript?
Most incidents are caused by either (a) the wrong experience firing due to priority, exclusivity, or targeting issues, or (b) Composer making a decision before entitlements are available.
Verify Which Experience Is Firing
-
Open the affected page in a clean session (Incognito/Private mode is best).
-
Append
?piano_debug=trueto the URL. -
Confirm the following:
-
An experience matches (vs. "No experience matched").
-
The expected experience ID is firing.
-
Cards are passing or failing for the reasons you expect (Effective Pages, segments, meters, resource check, etc.).
-
If you are using ?xpdebug=true, also check the browser console for evaluation logs and reasons why a card was skipped (e.g., container-not-found).
Common finding: The paywall experience qualifies, but a different higher-priority experience executes first and suppresses it via exclusivity.
Confirm Piano Scripts Are Loading
Quick Checks
-
In the browser console, type
tp.aid. It should return your application ID. If it returnsundefined, the Piano script did not load or did not initialize. -
In DevTools Network tab, verify successful (
200/304) delivery of:-
tinypass.min.js -
/xbuilder/experience/execute(Composer delivery)
-
Test for Network or Proxy Blocking
Run the following in the DevTools Console to test whether Piano scripts are being blocked:
((src) => new Promise((ok, fail) => {
const s = document.createElement('script');
s.src = src; s.async = true;
s.onload = () => ok('piano_loaded');
s.onerror = () => fail('piano_blocked');
document.head.appendChild(s);
}))('//cdn.piano.io/api/tinypass.min.js')
.catch(() => console.warn('Piano script was blocked'));
If blocked, whitelist (or allow via CSP) at minimum:
-
cdn.piano.io -
*.piano.io -
*.tinypass.com
Incognito/private mode note: Some browsers and privacy tools block known tracking domains more aggressively in private mode. If your paywall fails only in private mode, consider full domain whitelabeling (serving Piano assets from a first-party subdomain) or server-side enforcement for critical access control.
Validate Entitlements in the Browser
When a logged-in user sees a paywall, first confirm whether the browser actually has access information. Run the following in the DevTools Console:
tp = window.tp || [];
tp.push(["init", function() {
// Check if the user is logged in by retrieving user details
var user = tp.pianoId.getUser();
if (user) {
console.log("User is logged in:", user);
// Call the API to get the access list for the logged in user
tp.api.callApi("/access/list", {}, function(data) {
if (typeof data.data !== "undefined") {
console.log("User access data:", data.data);
// Loop through each access object in the response
for (var i in data.data) {
var access = data.data[i];
console.log("Access object:", access);
// Check if access is granted for this object
if (access.granted) {
console.log("Access granted for:", access);
} else {
console.log("Access NOT granted for:", access);
}
}
} else {
console.log("No access data returned.");
}
});
} else {
console.log("User is not logged in.");
}
}]);
Look for signals such as:
-
"User is not logged in." — The browser session doesn't recognize the user; the paywall is expected.
-
"No access data returned." — The user is logged in but Piano returned no access objects at all.
-
"Access NOT granted for:" — Access objects exist but none have granted: true, meaning the subscription may be expired or not linked to this resource.
-
"Access granted for:" — The user does have valid access, which suggests the paywall is being displayed incorrectly and the issue may lie in the offer template or experience configuration.
If Entitlements Are Missing or Empty
Typical causes include:
-
tinypass.min.jsor the entitlements call fails or times out (4xx/5xx), so the user is treated as anonymous. -
Cookies are missing, expired, or blocked
-
Login is occurring on a different domain or subdomain than where cookies are scoped.
Action: Test again in a clean session, disable content blockers, and check the Network panel for the entitlements request, reviewing its response timing and status code.
Fix Common Composer Configuration Problems
Show Template / Show Offer Without an Access Check
A Show Template action does not inherently verify entitlements. If you show a template to "All users" (or a broad segment), subscribers may see it too.
Recommended mitigations:
-
Add a Resource Check card early in the experience flow (e.g., "User does NOT have access to resource
Premium"). -
Or enable "Don't show to users with active access" in the template/offer card settings.
This applies to web and in-app implementations and is especially important for "template-only" experiences.
Resource-Check Logic Mismatch
If your bypass logic includes multiple resources, ensure the operator matches your intent:
|
Operator |
When to Use |
|---|---|
|
Any of |
Owning any one of the listed resources grants access. |
|
All of |
The user must own every listed resource to be granted access. |
Misconfigured logic can either over-block subscribers or accidentally grant access to users who should not have it.
Experience Priority and Exclusivity Conflicts
Check the following:
-
Experience priority ordering — especially if multiple experiences target overlapping traffic.
-
Action Exclusivity rules — "hide other experiences," exclusive action cards, etc.
-
Permanent/evergreen experiences (house ads, cookie banners, registration flows) that may run before paywalls.
Symptom: The expected paywall never appears, or a fallback/paywall appears instead of your intended dynamic offer.
Effective Pages Targeting Is Too Narrow
If the debug panel shows that no experience executed, Effective Pages rules often do not match due to:
-
Protocol or host mismatches
-
Missing wildcards
-
URL variations (mobile paths, UTM parameters, trailing slashes, etc.)
Where appropriate, broaden matching; for example, use more inclusive patterns or "ANY ONE OF" conditions to cover common URL variants.
Timing Issues: Composer Decides Before Identity Is Ready
If Composer evaluates too early, it may default to a paywall (or fallback experience) before login or entitlements are available.
SPA and Login Flows: Force Re-Evaluation
If the user logs in or subscribes without a full page reload, re-run Composer using tp.experience.execute().
A full location.reload() may also work.
Rendering Problems: Paywall Injected but Not Visible
If the experience qualifies but the paywall is not visible:
-
Inspect the DOM for the paywall container/iframe (e.g.
#paywall). -
Check computed CSS for rules such as:
-
display: none -
Hidden overlays or
z-indexconflicts
-
-
Review any site JavaScript that toggles visibility after specific events (e.g., after a meter expires).
Common symptom: The paywall appears only on the second pageview because site code changes display after a trigger rather than allowing the initial render.
For inline templates, also confirm the required container element exists in the DOM at the moment the template loads. Missing containers can prevent rendering entirely.
Site License (IP-Based Access) Troubleshooting
If site-license users see a paywall while on a corporate network:
-
Confirm Piano scripts and entitlement calls are not blocked by corporate proxy or DNS filtering.
-
Verify the organization's public IP address matches the site-license configuration (public IPs can change with ISP or network changes).