Open Paywall
Learn how to programmatically open the paywall using the paywall.open()
method. This allows you to trigger the paywall display at the right moment in your user flow.
Overview
The paywall.open()
method displays the paywall interface to users, allowing them to:
- Sign in to existing accounts
- Purchase subscriptions or make payments
- Restore previous purchases
- View subscription options and pricing
- Access trial offers if configured
Smart Opening Logic: The paywall will only open if country targeting settings match, trial period conditions are met, and the user doesn’t have an active subscription or lifetime payment.
Basic Usage
Simple Paywall Opening
try {
await paywall.open();
// Paywall has status visible
console.log('Paywall opened successfully');
} catch (error) {
// Paywall has status invisible
console.log('Paywall could not be opened:', error);
}
With Resolve Event
You can specify when the promise should resolve by passing a resolveEvent
parameter:
try {
await paywall.open({
resolveEvent: 'success-purchase'
});
// User completed a purchase
console.log('User successfully purchased subscription');
} catch (error) {
// User dismissed paywall without purchasing
console.log('Purchase was not completed');
}
Resolve Events
Control when the paywall.open()
promise resolves by specifying different events:
Resolve Event | Description | Use Case |
---|---|---|
opened | Promise resolves when the paywall opens | Just need to know the paywall appeared |
signed-in | Promise resolves when user signed in | Need user authentication for API access |
success-purchase | Promise resolves when user paid or restored purchase | Unlock premium features after payment |
Practical Examples
Authentication Flow
Wait for user to sign in before allowing API access:
const authenticateUser = async () => {
try {
await paywall.open({ resolveEvent: 'signed-in' });
// User is now authenticated
console.log('User authenticated successfully');
// Now you can make API requests
makeAPIRequest();
} catch (error) {
console.log('User did not authenticate');
}
};
Purchase Flow
Wait for successful purchase before unlocking features:
const unlockPremiumFeatures = async () => {
try {
await paywall.open({ resolveEvent: 'success-purchase' });
// User completed purchase
console.log('Purchase successful!');
// Unlock premium features
enablePremiumFeatures();
refreshUserSubscription();
} catch (error) {
console.log('Purchase was not completed');
showFreeFeatures();
}
};
React Integration
Complete React component example:
import { useState } from 'react';
function PaywallButton() {
const [isOpening, setIsOpening] = useState(false);
const [userStatus, setUserStatus] = useState('guest');
const handleOpenPaywall = async (resolveEvent) => {
setIsOpening(true);
try {
await paywall.open({ resolveEvent });
switch (resolveEvent) {
case 'opened':
console.log('Paywall opened');
break;
case 'signed-in':
setUserStatus('authenticated');
console.log('User signed in');
break;
case 'success-purchase':
setUserStatus('premium');
console.log('User purchased subscription');
break;
}
} catch (error) {
console.log(`Action ${resolveEvent} was not completed`);
// Handle specific reasons for paywall not opening
if (error.visibility_status_reason) {
switch (error.visibility_status_reason) {
case 'active-payment-found':
setUserStatus('premium');
console.log('User already has active subscription');
break;
case 'openings-trial':
case 'time-trial':
setUserStatus('trial');
console.log('User is in trial period');
break;
case 'closed-by-user':
console.log('User cancelled paywall');
break;
default:
console.log('Paywall unavailable:', error.visibility_status_reason);
}
}
} finally {
setIsOpening(false);
}
};
return (
<div>
<p>User Status: {userStatus}</p>
<button
onClick={() => handleOpenPaywall('signed-in')}
disabled={isOpening}
>
{isOpening ? 'Opening...' : 'Sign In'}
</button>
<button
onClick={() => handleOpenPaywall('success-purchase')}
disabled={isOpening}
>
{isOpening ? 'Opening...' : 'Upgrade to Premium'}
</button>
</div>
);
}
Display Modes
Fullscreen Mode (Default)
By default, the paywall opens in fullscreen mode:
// Opens in fullscreen
await paywall.open();
Container Mode
To open the paywall within a specific container, configure the paywallContainerId
in your installation script:
<script>
window.paywall = {
paywallContainerId: 'my-paywall-container',
// other configuration
};
</script>
Then create the container element in your HTML:
<div id="my-paywall-container"></div>
// This will now open within the specified container
await paywall.open();
When Paywall Won’t Open
The paywall has built-in logic to prevent unnecessary displays. When paywall.open()
fails, the error object contains a visibility_status_reason
property that explains exactly why the paywall couldn’t open.
Smart Prevention: The paywall automatically prevents opening when it’s not needed or appropriate. Always check error.visibility_status_reason
for the specific reason.
Visibility Status Reasons
The error.visibility_status_reason
property can have the following values:
'active-payment-found'
- User already has an active subscription or lifetime purchase'openings-trial'
- User is in trial period (action-based trial)'time-trial'
- User is in trial period (time-based trial)'country-not-match'
- User’s location doesn’t match targeting settings'visibility-turned-off'
- Paywall visibility is disabled in settings'closed-by-user'
- User manually closed the paywall'error'
- A technical error occurred
Handling Prevention
try {
await paywall.open();
// Paywall opened successfully
} catch (error) {
// Paywall was prevented from opening
console.log('Paywall could not open:', error.visibility_status_reason);
// Handle different scenarios based on reason
switch (error.visibility_status_reason) {
case 'active-payment-found':
showMessage('You already have an active subscription');
// Allow user to access premium features
enablePremiumFeatures();
break;
case 'openings-trial':
case 'time-trial':
showMessage('You are still in trial period');
// Allow user to use features during trial
enableTrialFeatures();
break;
case 'country-not-match':
showMessage('Paywall is not available in your region');
// Possibly show alternative options
showRegionalAlternatives();
break;
case 'visibility-turned-off':
showMessage('Service is temporarily unavailable');
// Allow free access when paywall is disabled
enableFreeAccess();
break;
case 'closed-by-user':
// User cancelled, no message needed
break;
default:
showMessage('Unable to open paywall');
}
}
Advanced Usage
Conditional Opening
Check conditions before opening:
const openPaywallIfNeeded = async () => {
// Check if user needs paywall
const userSubscription = await checkUserSubscription();
if (userSubscription.active) {
console.log('User already has subscription');
return;
}
if (userSubscription.trial && userSubscription.trialDaysLeft > 0) {
console.log('User still has trial time');
return;
}
// User needs subscription - open paywall
try {
await paywall.open({ resolveEvent: 'success-purchase' });
console.log('User upgraded successfully');
} catch (error) {
console.log('User did not upgrade');
}
};
Multiple Resolve Events
Handle different outcomes with separate functions:
const handleAuth = async () => {
try {
await paywall.open({ resolveEvent: 'signed-in' });
onUserAuthenticated();
} catch (error) {
onAuthenticationFailed();
}
};
const handlePurchase = async () => {
try {
await paywall.open({ resolveEvent: 'success-purchase' });
onPurchaseSuccess();
} catch (error) {
onPurchaseFailed();
}
};
Chaining Operations
Wait for authentication, then prompt for purchase:
const fullOnboardingFlow = async () => {
try {
// First, ensure user is authenticated
await paywall.open({ resolveEvent: 'signed-in' });
console.log('User authenticated');
// Show some onboarding content
await showOnboarding();
// Then prompt for subscription
await paywall.open({ resolveEvent: 'success-purchase' });
console.log('User subscribed');
// Complete onboarding
completeOnboarding();
} catch (error) {
console.log('Onboarding incomplete:', error);
}
};
Best Practices
1. Use Appropriate Resolve Events
// For authentication flows
await paywall.open({ resolveEvent: 'signed-in' });
// For feature unlocking
await paywall.open({ resolveEvent: 'success-purchase' });
// For simple display tracking
await paywall.open({ resolveEvent: 'opened' });
2. Provide User Feedback
const [showingPaywall, setShowingPaywall] = useState(false);
const openPaywall = async () => {
setShowingPaywall(true);
try {
await paywall.open({ resolveEvent: 'success-purchase' });
showSuccessMessage('Welcome to Premium!');
} catch (error) {
// User cancelled or error occurred
} finally {
setShowingPaywall(false);
}
};
3. Handle All Outcomes
const requestPremiumAccess = async () => {
try {
await paywall.open({ resolveEvent: 'success-purchase' });
// Success: unlock features
unlockPremiumFeatures();
trackEvent('subscription_purchased');
} catch (error) {
// Handle based on specific reason
switch (error.visibility_status_reason) {
case 'active-payment-found':
// User already has subscription - grant access
unlockPremiumFeatures();
trackEvent('subscription_already_active');
break;
case 'openings-trial':
case 'time-trial':
// User is in trial - allow limited access
unlockTrialFeatures();
trackEvent('trial_access_granted');
break;
case 'closed-by-user':
// User declined - show limited features
showLimitedFeatures();
trackEvent('subscription_declined');
break;
default:
// Other reasons - show appropriate message
showLimitedFeatures();
trackEvent('paywall_unavailable', { reason: error.visibility_status_reason });
}
}
};
4. Smart Feature Access Control
// Helper function to determine if user should have access
const shouldAllowAccess = (error) => {
const allowedReasons = [
'active-payment-found', // User has subscription
'openings-trial', // Trial active
'time-trial', // Trial active
'visibility-turned-off' // Paywall disabled
];
return allowedReasons.includes(error.visibility_status_reason);
};
// Usage in feature gating
const accessPremiumFeature = async () => {
try {
await paywall.open({ resolveEvent: 'success-purchase' });
// User purchased - full access
return true;
} catch (error) {
// Check if we should allow access anyway
return shouldAllowAccess(error);
}
};
Use Cases
Feature Gating
const accessPremiumFeature = async () => {
if (!userHasPremium()) {
try {
await paywall.open({ resolveEvent: 'success-purchase' });
// User purchased - show premium feature
showPremiumFeature();
} catch (error) {
// Check if we should allow access based on reason
switch (error.visibility_status_reason) {
case 'active-payment-found':
case 'openings-trial':
case 'time-trial':
case 'visibility-turned-off':
// Allow access in these cases
showPremiumFeature();
break;
case 'closed-by-user':
case 'country-not-match':
default:
// Don't allow access, show alternative
showFreeAlternative();
}
}
} else {
// User already has premium
showPremiumFeature();
}
};
Trial Expiration
const handleTrialExpiration = async () => {
showMessage('Your trial has expired');
try {
await paywall.open({ resolveEvent: 'success-purchase' });
showMessage('Welcome back! Your subscription is active.');
} catch (error) {
showMessage('Continue with limited features');
}
};
Onboarding Flow
const completeOnboarding = async () => {
// Show app features
await showFeatureTour();
// Encourage subscription
const wantsUpgrade = await showUpgradePrompt();
if (wantsUpgrade) {
try {
await paywall.open({ resolveEvent: 'success-purchase' });
showWelcomeMessage();
} catch (error) {
showTrialMessage();
}
}
};