Skip to main content

Custom Screen after a Paywall

In onboarding flows, you need to automatically pass a user’s email from the payment provider to a Custom screen

A
Written by Alexander Karpovich
Updated this week

Case

Process the payment first and then request the user’s email, establishing the linkage between the transaction and the user account afterward.

Solution

Use custom analytics to save the userEmail into window.customData from localStorage (under the key oo-user-email) at the moment of a successful purchase.


Example of Analytics

<script> window.customEventTracker = (event, args) => { const successPaymentEvents = [ 'StripePurchase', 'StripeStartTrial', 'StripeSubscribe', 'PaddleSubscribe', 'PaddlePurchase', 'PaddleStartTrial', ]; if (successPaymentEvents.includes(event) && localStorage.getItem('oo-user-email')) { const userEmail = localStorage.getItem('oo-user-email'); window.customData ??= {}; window.customData.userEmail = userEmail; } }; </script>

Example of a Custom Screen

<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8' />
<title>Some custom screen</title>
<!-- OO-PORT-MESSAGE-HANDLER-START -->
<!-- DO NOT REMOVE THIS CODE -->
<script>
window.onboardingOnlineCustomScreenData = {
inputs: {},
labels: {},
userData: {},
customData: {},
};
window.nextScreen = () => {};
window.backScreen = () => {};
window.acceptCookie = () => {};
window.declineCookie = () => {};



function renderEmailBadge() {
const email = (window.onboardingOnlineCustomScreenData?.customData || {}).userEmail;
const emailEl = document.getElementById('user-email');
const avatarEl = document.getElementById('user-avatar');
const container = document.getElementById('email-card');



if (!container) return; // safety



if (email) {
emailEl.textContent = email;
const initial = (email.trim()[0] || '?').toUpperCase();
avatarEl.textContent = initial;
container.classList.remove('hidden');
} else {
// If userEmail is absent, keep the card hidden (no layout shift)
container.classList.add('hidden');
}
}



window.onmessage = (event) => {
if (typeof event.data === 'object' && event.data.eventName === 'InitCustomScreen') {
window.onboardingOnlineCustomScreenData.inputs = event.data.args.inputs;
window.onboardingOnlineCustomScreenData.labels = event.data.args.labels;
window.onboardingOnlineCustomScreenData.userData = event.data.args.userData;
window.onboardingOnlineCustomScreenData.customData = event.data.args.customData;



// Render the email (if provided) right after we receive data
try { renderEmailBadge(); } catch (e) { /* no-op */ }



const port = event.ports[0];
if (port) {
window.nextScreen = (args) => {
port.postMessage(
{
eventName: 'NextScreen',
args,
},
[]
);
};
window.backScreen = () => {
port.postMessage(
{
eventName: 'BackScreen',
args: null,
},
[]
);
};
window.acceptCookie = () => {
port.postMessage(
{
eventName: 'AcceptCookie',
args: null,
},
[]
);
};
window.declineCookie = () => {
port.postMessage(
{
eventName: 'DeclineCookie',
args: null,
},
[]
);
};
}
}
};
</script>
<!-- OO-PORT-MESSAGE-HANDLER-END -->
<style>
:root {
--card-bg: #0f172a0d;
--border: #e2e8f0;
--text: #0f172a;
--muted: #64748b;
--accent: #2563eb;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
color: var(--text);
background-color: #ffffff;
}
.page {
min-height: 100vh;
display: grid;
place-items: center;
padding: 24px;
}
.stack { display: grid; gap: 16px; }



/* Email card */
.card {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 16px;
backdrop-filter: blur(4px);
}
.avatar {
width: 36px;
height: 36px;
border-radius: 999px;
display: grid;
place-items: center;
font-weight: 700;
border: 1px solid var(--border);
background: #fff;
}
.meta { line-height: 1.25; }
.label { font-size: 12px; color: var(--muted); }
.value { font-size: 14px; font-weight: 600; word-break: break-all; }



.hidden { display: none; }



/* Form */
#custom-form {
display: grid;
gap: 16px;
justify-items: center;
}
.btn {
appearance: none;
border: 1px solid var(--border);
background: #fff;
padding: 10px 16px;
border-radius: 12px;
font-weight: 600;
cursor: pointer;
transition: transform .05s ease, box-shadow .2s ease;
box-shadow: 0 1px 0 0 rgba(2,6,23,.05);
}
.btn:active { transform: translateY(1px); }
.btn-primary {
border-color: transparent;
background: var(--accent);
color: #fff;
box-shadow: 0 6px 14px rgba(37,99,235,.25);
}
</style>
</head>
<body>
<main class="page">
<div class="stack">
<!-- Email badge card (auto-hides when userEmail is absent) -->
<div id="email-card" class="card hidden">
<div id="user-avatar" class="avatar" aria-hidden="true">?</div>
<div class="meta">
<div class="label">Signed in as</div>
<div id="user-email" class="value" aria-live="polite"></div>
</div>
</div>



<form id='custom-form'>
<button id='submit-button' class="btn btn-primary" type="submit">Next screen</button>
</form>
</div>
</main>



<script>
const form = document.getElementById('custom-form');
form.addEventListener('submit', function (event) {
event.preventDefault();
window.nextScreen({ someField: 'someValue' });
});
</script>
</body>
</html>

🔗 Example: Custom screen demo


Important note:

  • If you are using Stripe PaymentElement or ExpressCheckout where email isn't required - make sure to include a funnel step that collects the email. This ensures PlatformUser and StripeCustomer are updated, and subscriptions can be found by this Email.

  • For Paddle the email is always required.

Did this answer your question?