Web
Read entitlements, record experiment exposures and start Stripe checkouts from a browser.
Before you install: allow your origin
A Rovenue API key is not usable from a browser until you list the origins your
app is served from. This is the first thing to do: every request from an origin
that is not on the key's list is refused with 403
ORIGIN_NOT_ALLOWED before it
does anything. The SDK throws it as a RovenueApiError with that code, so the
cause is readable in your app rather than a bare CORS failure.
In the dashboard, open SDK & API → API keys, and add each origin to the key
you will ship — for example https://app.example.com and, for local work,
http://localhost:3000. Origins are matched exactly: scheme, host and port.
There are no wildcards, because a https://*.example.com entry would turn a
subdomain takeover into a working API key.
Install
npm install @rovenue/web-sdkConfigure
import { configure, createStorage } from "@rovenue/web-sdk";
export const rovenue = configure({
apiKey: "rov_pub_...", // your PUBLIC key — visible in the browser by design
apiUrl: "https://api.rovenue.io",
storage: createStorage(), // localStorage where available, memory where not
});Create the client once and share it. Constructing it per render rebuilds the identity, the entitlement cache and the event queue each time.
Choosing an app user id
The SDK generates its own identifier and uses it on every request. If you call
identify() with an id of your own, that id becomes guessable to anyone holding
your public key — which is visible in your page source.
So: never use an email address, a sequential number, or anything else a stranger could construct. Use an opaque id your backend maps to the user. The SDK warns in the console when it sees an email-shaped id.
rovenue.identify("u_9f3a1c7e"); // opaque
// rovenue.identify("ada@example.com"); // warns — guessableidentify() is client-local. Merging two subscribers is a server-side operation
with your secret key, never something a browser can do.
Entitlements
const entitlements = await rovenue.getEntitlements();The SDK caches the last answer, so a returning subscriber does not see a flash of the paywall they already paid to remove:
const cached = rovenue.getCachedEntitlements(); // synchronous, no networknull and {} mean different things and the SDK keeps them apart: {} is a
real answer meaning this person has no entitlements, null means we have not
found out. When the network is unreachable or the API returns a 5xx,
getEntitlements() answers from cache. When it returns a 4xx — a revoked key, a
rejected origin — it throws instead, so a misconfiguration surfaces rather than
hiding behind stale data.
React
import { RovenueProvider, useEntitlements } from "@rovenue/web-sdk/react";
function App() {
return (
<RovenueProvider client={rovenue}>
<Content />
</RovenueProvider>
);
}
function Content() {
const { entitlements, isLoading, error } = useEntitlements();
if (entitlements?.pro) return <Pro />;
return <Upsell loading={isLoading} error={error} />;
}Checkout
The web SDK has no purchase() — there is no native store sheet in a
browser. checkout() is where a web purchase flow ends: it starts a Stripe
Checkout session and hands you the URL to redirect to. This is the one
place the web quickstart genuinely diverges from the native SDKs' flow
(getOfferings() → purchase()).
const { offerings } = await rovenue.getOfferings();
const offering = offerings.find((o) => o.isDefault) ?? offerings[0];
const session = await rovenue.checkout({
offeringIdentifier: offering.identifier,
packageIdentifier: offering.packages[0].packageIdentifier,
successUrl: "https://app.example.com/welcome",
cancelUrl: "https://app.example.com/pricing",
});
window.location.assign(session.url);packageIdentifier is the package slot (packages[i].packageIdentifier), not
the product identifier.
successUrl and cancelUrl must be on one of the API key's allowed
origins, an exact match of scheme, host and port. That includes
http://localhost:<port> while developing, if it is listed. Otherwise they
must be on a verified custom domain over https.
You name a package, never a price. The amount is resolved on the server from
the offering that package belongs to, so a tampered page cannot change what is
charged. Pass an idempotencyKey if a double-submit is possible. It is sent as
the Idempotency-Key header, and the server hashes it together with your
project and the subscriber before using the digest as Stripe's idempotency key,
so the same key from two different buyers never returns the same session.
Repeating the call with the same key for the same buyer returns the session
already created. Without a key, the server derives one from the buyer, the
package, the price and a 5-minute window, so rapid re-posts still converge on
one session.
Events
rovenue.track({ eventType: "pricing_viewed" });Queued and delivered at least once: an event is dropped only after the server acknowledges it, unsent events survive a page reload, and the queue flushes when the tab is hidden or closing. Each event carries a stable id so a replay is deduplicated rather than double-counted.
Under React the provider starts and stops the queue for you. Elsewhere, call
rovenue.startEventQueue() once after configuring.
Server-side rendering
The SDK is safe to import and construct on a server — Next.js, Remix and similar
frameworks run this code where there is no window and no localStorage.
Nothing touches the DOM at import time, and storage falls back to memory when it
is unavailable or blocked.
Network calls still belong on the client. Construct on the server if it suits your app's shape; fetch when you have a viewer.
Supported browsers
Any browser with fetch, localStorage and WebCrypto. WebCrypto is only exposed
on secure origins, so experiment assignment needs https (or
http://localhost); the SDK says so explicitly rather than silently bucketing
everyone into one variant.
Funnels
Web funnels are hosted at a URL and embedded — the web SDK's surface is
client, storage, cache and events (packages/sdk-web/src/index.ts:7-11), so it draws no funnel. Embed the project id and slug: Hosting.
const frame = document.createElement("iframe");
frame.src = "https://funnels.example.com/f/prj_demo1/summer-quiz";
document.body.appendChild(frame);What's next
- Entitlements — read and gate on active subscriptions.
- Placements — how a placement resolves to a funnel or an A/B experiment variant.
- SDK API Reference — the generated TypeDoc reference for every exported type and method, at
/api/sdk-web/. Reference: Methods and Reference: Types cover the React Native/Swift/Kotlin SDKs only — the web SDK's surface differs too much (nopurchase(), no StoreKit/Play Billing bridge) to share those tables, so/api/sdk-web/is the accurate reference here.