Visitor Authentication
Visitor authentication determines how a visitor proves who they are before Ezoic Subscriptions grants access or starts checkout. There are two modes:
- Ezoic visitor accounts — the default. Ezoic provides sign-in, account creation, and recovery. No integration code.
- Bring your own login — connect your existing login system to the widget through an
AuthAdapter.
You select the mode in your Ezoic dashboard. The default is Ezoic visitor accounts.
How Sign-In Works
In both modes the widget resolves a visitor identity (an email address) and uses it for access checks (hasAccess) and checkout. What differs is who handles sign-in and account creation:
- Ezoic visitor accounts — Ezoic's built-in login and create-account screens, shown in the widget.
- Bring your own login — your own login and sign-up pages, reached through the callbacks you register during widget setup.
At checkout, a signed-in visitor goes straight to payment. An anonymous visitor sees an identity gate offering Log in, Create account, and — when guest checkout is enabled — Continue as a guest. Where Log in and Create account send the visitor depends on the active mode: in Ezoic visitor accounts mode they open Ezoic's built-in login and create-account screens inside the widget; in bring-your-own-login mode they call the callbacks you supply, sending the visitor to your own login and sign-up pages.
Returning visitors sign in the same way, through the active mode's provider. As a final fallback — so a paying visitor is never left without access, for example when they return on a new device — Ezoic Subscriptions can email a one-time, passwordless access link. This is a safety net, not the primary way to sign in.
To let returning subscribers sign in from your own navigation — without first reaching a paywall — add a login entry point with ezsubscriptions.login() or the data-ezoic-login attribute. It opens the same provider as checkout for your mode, and on Ezoic visitor accounts the page unlocks in place once they sign in.
Subscribers manage their billing in the subscriber portal at https://subscriber.ezoic.com.
Signing Out
Signing out ends the visitor's Subscriptions access session, and gated content re-locks in place. How you trigger it depends on your mode.
ezsubscriptions.disableAds(), signing out restores ads too. Because ad suppression is applied on Ezoic's servers, a page refresh may be needed before ads begin showing again.
Ezoic visitor accounts
Call ezsubscriptions.logout(), or add the data-ezoic-logout attribute to a "Log out" link. Ezoic handles the rest behind the scenes — it signs the visitor out of their Ezoic visitor account, clears the access session, and re-locks gated content in place, with no extra code:
<button type="button" data-ezoic-logout>Log out</button>
or from your own code:
await ezsubscriptions.logout();
Bring your own login
Your login system owns the visitor's session, so sign them out of it first, then tell the widget to drop the access session:
async function onLogout() {
await myAuth.signOut(); // end the visitor's session in your own system
await ezsubscriptions.logout(); // drop the Subscriptions access session
}
ezsubscriptions.logout() clears the access session, discards any parked checkout, and fires access:change. Calling ezsubscriptions.authChanged() after your own sign-out also clears the access session and fires access:change, but it leaves ad state to you — so prefer logout() when the subscriber had ads removed. The widget cannot sign a visitor out of your own login system — that step is always yours.
The subscriber portal signs out separately
The subscriber portal at https://subscriber.ezoic.com is a separate, Ezoic-hosted area with its own sign-in and sign-out. Signing out of your site does not sign a visitor out of the portal, and vice versa.
Ezoic Visitor Accounts
This is the default mode and requires no integration code. Ezoic provides login, account creation, guest checkout (when you enable it), and passwordless recovery directly in the widget. You do not build or maintain any authentication on your site. If a visitor tries to create an account with an email that already has one, the widget links them to sign in with that email prefilled.
Continue with Google appears automatically on the login and account screens when it's available for the visitor, letting them sign in with their Google account. There's nothing to configure, and the option hides itself when it isn't available.
The widget places the Ezoic Accounts integration on the page for you. If your site already runs the Ezoic Accounts integration (window.ezAuth), leave it in place: the widget detects and reuses an existing instance and otherwise loads it itself. Either way, there is nothing to add or remove.
Bring Your Own Login
Use this mode when your site already has its own login system. You connect it to the widget by registering an AuthAdapter — a small object that lets the widget read the signed-in visitor's email and send visitors to your login and sign-up pages.
The AuthAdapter Contract
For the related SDK methods and their full signatures — initialize, authChanged, and the rest of the methods — see the JavaScript API Reference.
interface AuthAdapter {
getUserEmail?: () => string | null | Promise<string | null>;
getIdentityToken?: () => string | null | Promise<string | null>;
goToLogin: () => void | Promise<void>;
goToCreateAccount: () => void | Promise<void>;
logout?: () => void | Promise<void>;
}
goToLogin and goToCreateAccount are always required. For identity, provide either getIdentityToken() (preferred — keeps the email off the page; see Keeping the email off the page) or getUserEmail(). Provide both and the token is used when available, with getUserEmail() as the fallback.
logout() is optional. Provide it to show a Not you? switch next to the visitor's identity at checkout, so they can sign out of your auth and switch accounts. Without it the switch is hidden — the widget cannot sign visitors out of your system itself.
getUserEmail()
Returns the signed-in visitor's email address, or null when no one is signed in. May be synchronous or return a promise.
Returns:
string | null | Promise<string | null>
The widget calls this to detect a logged-in visitor and skip the checkout identity gate. Return a syntactically valid email when the visitor is authenticated in your system; return null (or an empty/invalid value) when the visitor is anonymous.
getIdentityToken() instead — it identifies the same visitor without putting the email on the page.
getIdentityToken()
Optional. Use this when you don't want the visitor's email exposed to your page's JavaScript.
getUserEmail() hands the email to the page as plain text, so any script running on the page can read it. getIdentityToken() avoids that: instead of the email, the widget gets a short-lived signed token that stands in for it. The email is encrypted inside the token, so nothing on the page can read it.
When getIdentityToken() returns a token, the widget uses it for sign-in and checkout instead of an email. When it returns null (visitor anonymous, or the token fetch failed), the widget falls back to getUserEmail() if you provided it — otherwise the visitor is treated as anonymous and sees the email step.
Returns:
string | null | Promise<string | null>
You don't build or sign the token yourself — Ezoic Subscriptions does. Your server calls the REST API's POST /subscriptions/v1/identity-token with the signed-in reader's email, and the API returns a signed token. getIdentityToken() then retrieves that token from a small endpoint on your own server:
auth: {
getIdentityToken: async function () {
// Your server-side endpoint calls POST /subscriptions/v1/identity-token
// with the signed-in reader's email and returns { token }.
const res = await fetch("/api/subscriptions-identity-token", { credentials: "same-origin" });
if (!res.ok) return null;
const { token } = await res.json();
return token ?? null;
},
getUserEmail: function () {
// Optional — used when getIdentityToken() returns null.
return myAuth.getCurrentUser()?.email ?? null;
},
goToLogin: function () { /* ... */ },
goToCreateAccount: function () { /* ... */ },
},
Don't cache the token. It expires after one hour, and the widget calls getIdentityToken() whenever it needs one. Retrieve a new token on every call — don't embed one in the page. A stale token is rejected and the visitor falls back to the email step.
If your pages don't run third-party scripts, skip this — getUserEmail() alone is fine.
goToLogin()
Sends the visitor to your login experience. The adapter decides how — a navigation, a modal, or a provider SDK call.
Returns:
void | Promise<void>
Powers the gate's Log in action and the paywall's "Already subscribed? Log in" link.
goToCreateAccount()
Sends the visitor to your account creation experience. The adapter decides how, exactly like goToLogin.
Returns:
void | Promise<void>
Powers the gate's Create account action. It can point at the same destination as goToLogin when your login page also registers new accounts.
Register the Adapter
Register the adapter once with ezsubscriptions.initialize({ auth }) inside the cmd queue, so it is set as soon as the script is ready. On a single-page app, run it once in your root layout or shell — not per route:
<script>
window.ezsubscriptions = window.ezsubscriptions || {};
ezsubscriptions.cmd = ezsubscriptions.cmd || [];
ezsubscriptions.cmd.push(function (api) {
api.initialize({
auth: {
getUserEmail: function () {
// Return the signed-in visitor's email from your own auth, or null.
return myAuth.getCurrentUser()?.email ?? null;
},
goToLogin: function () {
window.location.href = "/login?return=" + encodeURIComponent(location.href);
},
goToCreateAccount: function () {
window.location.href = "/signup?return=" + encodeURIComponent(location.href);
},
},
});
});
</script>
<script src="https://sm.ezoic.com/min.js" async defer></script>
Registration is idempotent: calling initialize again with a valid adapter replaces the previous one.
initialize only registers the adapter — it does not read your login state or start a session. That happens when you call authChanged(). Until then the widget treats the visitor as anonymous, even if they are signed in to your site.
Signal Authentication Changes
Call ezsubscriptions.authChanged() when your auth system knows the visitor's login state — after a login, a logout, or after a session restore finishes. It reads the adapter, refreshes the access session, and resumes any checkout they left to log in:
async function onLogin() {
await myAuth.signIn(/* ... */);
await ezsubscriptions.authChanged();
}
The widget does not watch your login system, and initialize does not read it. Page load does not either: the signed-in visitor is only applied when you call authChanged().
If login happened on another page, send them back to the URL they started from first (the examples above pass a return parameter). Then call authChanged() on that page once getUserEmail() or getIdentityToken() would return the signed-in visitor:
- Already true on first paint (session cookie, server-rendered page): call it after
initialize, for example in the samecmdcallback. - Not true yet (the page loads anonymous, then a request fills in the session): wait for that request. Calling
authChanged()on the checkout page while the adapter still returns empty is treated as logged out and discards the parked checkout.
A parked checkout only resumes during authChanged(), and only on the page checkout started on.
goToLogin() or goToCreateAccount() sends a visitor to your pages, bring them back to the URL they started from once they sign in — the examples above pass a return parameter for exactly this. Call authChanged() on that page after your auth system has the signed-in visitor. A resumed checkout only re-opens on the page it started on, so a visitor who lands anywhere else has to find their way back themselves.
For signing a visitor out, see Signing Out. For re-checking access on client-side route changes, see Single-Page Apps and Frameworks.
How Checkout Uses the Adapter
- A signed-in visitor (
getIdentityToken()returns a token, orgetUserEmail()returns an email) skips the identity gate and goes straight to payment. - An anonymous visitor sees Log in, Create account, and — when guest checkout is enabled — Continue as guest.
goToLogin()andgoToCreateAccount()send the visitor to your login; when they are back on the checkout page and you callauthChanged()after the adapter returns the signed-in visitor, the widget resumes the same checkout. - The token or email from the adapter is the visitor's identity for access checks and checkout. The token is used when it returns one; otherwise the email.
ezsubscriptions.initialize({ auth }) has registered a complete adapter (goToLogin, goToCreateAccount, and an identity method). Until then, checkout logs an error and does not complete. Configure and test your adapter before switching the domain to this mode.
Guest Checkout
Guest checkout lets a visitor buy with just an email address instead of signing in or creating an account. At the identity gate the visitor chooses Continue as guest, enters an email, and pays. Access attaches to that email, so the visitor can sign in or recover access later using the same address.
If a guest later registers on your site (or already has an account) using that same email, their account — on your own login system or Ezoic visitor accounts, whichever mode you use — is linked to the purchase automatically, with no extra work for you or the visitor.
To make that next return a quick sign-in instead of another email link, the confirmation screen after a guest purchase offers an optional, skippable step to create an account. On Ezoic visitor accounts the visitor creates an Ezoic account right in the widget; on bring your own login it sends them to your own sign-up page. Either way, an account created with the same email keeps the purchase automatically — so encourage guests to reuse their checkout email.
Guest checkout is on by default. It is a per-domain setting in your Ezoic dashboard — under your checkout / visitor sign-in settings, as Allow guest checkout — and applies to both authentication modes. Turn it off there to require every buyer to sign in or create an account before paying.
Next Steps
- Install the widget with Onsite Script Integration.
- Gate content with the Publisher-Managed Access API.
- Review method options in the JavaScript API Reference.