Skip to Content

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 EventDescriptionUse Case
openedPromise resolves when the paywall opensJust need to know the paywall appeared
signed-inPromise resolves when user signed inNeed user authentication for API access
success-purchasePromise resolves when user paid or restored purchaseUnlock 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:

  1. 'active-payment-found' - User already has an active subscription or lifetime purchase
  2. 'openings-trial' - User is in trial period (action-based trial)
  3. 'time-trial' - User is in trial period (time-based trial)
  4. 'country-not-match' - User’s location doesn’t match targeting settings
  5. 'visibility-turned-off' - Paywall visibility is disabled in settings
  6. 'closed-by-user' - User manually closed the paywall
  7. '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(); } } };

Next Steps

Last updated on