Paywall / Store
Store
Your Store is a hosted pricing page — one card per plan, styled under Store page and Branding. The web way to show it is to share its URL or embed it as an iframe — no SDK. Native apps can show it in-app (see below) — with one important exception on iOS and the Mac App Store, where Apple requires a different shape.
Show the store on your website
Every app has a hosted store at a public URL. Share it directly, or embed it — these are the exact snippets your dashboard's SDK → Store tab generates (same source, pre-filled with your app's slug). Replace YOUR_APP_SLUG with your app's slug.
Hosted store page — share this URL
https://onelo.tools/customer/app/YOUR_APP_SLUG
Embed · Full-width (follows browser width — for the multi-column desktop store)
<iframe
id="onelo-embed-store-YOUR_APP_SLUG"
src="https://onelo.tools/customer/app/YOUR_APP_SLUG"
style="width:100vw;max-width:100vw;margin-left:calc(50% - 50vw);border:none;min-height:480px;"
loading="lazy"
></iframe>
<script>
// Auto-resize the iframe height to its content (no inner scrollbar).
window.addEventListener('message', function (e) {
if (e.origin !== "https://onelo.tools") return;
var d = e.data || {};
if (d.type === 'onelo:resize' && d.height) {
var f = document.getElementById('onelo-embed-store-YOUR_APP_SLUG');
if (f) f.style.height = d.height + 'px';
}
});
</script>Embed · In-container (fills its slot — keeps the store inside your page column)
<iframe
id="onelo-embed-store-YOUR_APP_SLUG"
src="https://onelo.tools/customer/app/YOUR_APP_SLUG"
style="width:100%;border:none;min-height:480px;"
loading="lazy"
></iframe>
<script>
// Auto-resize the iframe height to its content (no inner scrollbar).
window.addEventListener('message', function (e) {
if (e.origin !== "https://onelo.tools") return;
var d = e.data || {};
if (d.type === 'onelo:resize' && d.height) {
var f = document.getElementById('onelo-embed-store-YOUR_APP_SLUG');
if (f) f.style.height = d.height + 'px';
}
});
</script>postMessage listener that auto-resizes the iframe to its content (no inner scrollbar). The store adapts on its own: it shows your plans normally, and while Waitlist mode is on it redirects visitors to your waitlist (invited users with a token go straight to checkout)./buy/… returns 404 — an app with the paywall off is free, so there's nothing to buy.Show the store in a native app
In a native app (Swift, Android, Flutter, React Native, Electron) you don't embed an iframe — the store shows in-app, driven by Auth. Turn on Paywall → Store → Access Gate → “Plan required”, and the Onelo sign-in view (loadAuthView() / OneloAuthView) routes sign-up → store automatically: any user without an active plan buys before they reach the app. The Stripe card page hands off to the system browser. Set this up on the Auth tab — there's nothing extra to call.
On iOS and the Mac App Store, Onelo closes this flow by default.
Every route that ends at the store is refused for those platforms unless you say otherwise: sign-in and sign-up stop short of it, store-initiate and upgrade-initiate answer 409 in_app_store_not_allowed, and both the customer portal and the manage page hide “Change plan” and “View plans”. Cancelling, invoices and refunds keep working everywhere — those are your buyer's own interests, and we never block them.
In your own code, openUpgrade() becomes a no-op that logs the reason, and openStore() / initiateStoreFlow() raise in_app_store_not_allowed rather than opening a WebView — so catch it wherever you already handle store errors.
The switch is Paywall → Store → Access Gate, on the app itself. It is set per platform, so your iOS app can be closed while your web app sells normally — but note that “Sign in only” is not an iOS-only mode: whichever app you set it on stops selling in-app entirely. Choosing “Show the store” hands you responsibility for App Review. Full behaviour on the Access Gate reference.
Apple App Store: this flow needs a different shape
The in-app store above is fine on macOS distributed outside the Mac App Store, on Windows and Linux, and on the web — Apple's rules don't reach those.
On iOS and the Mac App Store, App Review guideline 3.1.1 requires purchases of digital content to go through Apple's own in-app purchase. Showing prices and a buy flow inside the app is not permitted, and “Plan required” will put you there.
The shape that works everywhere: sell on your website, and let the app sign the buyer in. Your website keeps the Stripe checkout, the VAT handling and the invoice; the app carries sign-in only — no prices, no buy button, no link to your pricing page. The purchase is recognised the moment they sign in, because it's the same Onelo account.
Two regional exceptions, both narrower than they look. On the US storefront, linking out to your own site is currently allowed with no entitlement and no Apple commission — but that is a court order under appeal, not Apple policy, so don't build on it permanently. In the EU, linking out is allowed with the StoreKit External Purchase Link Entitlement, but Apple still charges roughly 17–20%, which is more than the 15% Small Business Program rate for in-app purchase — so for most small developers it isn't worth it.
Not permitted at all on iOS: linking out from the app to buy in the UK, Canada, Australia and every other storefront.
If your app is entirely behind a paid plan, also give it a working free tier — App Review rejects apps that do nothing until a purchase it cannot see.
The shape we recommend for iOS: one product, two apps
Create an umbrella app in Applications, then attach two apps to it: your iOS or macOS app, and a web app. They share one user base, one store and one set of plans, so a purchase made on the website is recognised the moment the buyer signs into the app — same account, same password, nothing to sync.
This is what makes the compliant shape practical rather than painful. The website does the selling, the VAT and the invoice. The app does sign-in. And the parts your buyer is entitled to — cancelling, changing plan, downloading invoices, asking for a refund — live in the customer portal on the website, so your iOS app never has to offer them, or link to them.
What to say in the app when there is no plan
Guideline 3.1.3 forbids steering a user to buy outside Apple's in-app purchase — no link, no price, no “get it on our website”, not even in passing. What is allowed is stating a fact. Two situations, two different sentences; copy them as they are.
They have a subscription and want to manage it — bought anywhere, since the account is shared:
This subscription can't be changed in this app because it was purchased elsewhere. Sign in to your account where you made the purchase.
They have no plan at all. This is the state Onelo hands you after a successful sign-in on an Apple platform — a valid session and an empty entitlement — so gate your paid UI on entitlement and show this where the paid part would have been.
A plan is required to use this app. Plans aren't available for purchase in this app.
The second message is a dead end for the user, and we would rather say so than pretend otherwise. There is no wording that fixes it, because the thing that would help — telling them where to buy — is exactly what the guideline forbids.
The way out is product, not copy: give the app a working free tier, so somebody who signs in without a plan still has an app in front of them. An app that does nothing until a purchase it can't show gets rejected under 2.3.1 anyway.
Gate paid UI on entitlement
Once a buyer has paid, gate your paid UI on their entitlement — it's active | none, not a plan string or product id on the user:
// Client (Swift / JS / …): snapshot of the session's entitlement — no network, // kept fresh by the SDK's live connection (revoke pushes over SSE) const hasAccess = await onelo.auth.hasActiveAccess()
For a server-trusted check, verify the user's token on your backend — the GET /api/sdk/auth/user response carries entitlement.
For per-feature / per-tier gating (not just paid-or-not), use the Features SDK — the backend resolves each feature against the user's real plan. Never compare plan names client-side.