Rovenue SDK
Platforms

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-sdk

Configure

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 — guessable

identify() 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 network

null 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 (no purchase(), no StoreKit/Play Billing bridge) to share those tables, so /api/sdk-web/ is the accurate reference here.

On this page