Skip to content

Client vs Server Enforcement

Every team evaluating RevTurbine asks the same question: “If entitlements are checked in the browser, what stops a user from bypassing them?” This page is the one answer — the division of responsibility, and which tool to use where.

  • RevTurbine is authoritative for monetization policy. Which plans exist, what each plan entitles, when a placement shows — that lives in your Playbook and the control plane, and a PM changes it with no code deploy.
  • Your app owns enforcement. RevTurbine tells you the policy answer; your app is what actually allows or blocks the action.
  • Placements are additive; checks are fail-closed. If RevTurbine is paused, misconfigured, or unreachable, slots render nothing — so our downtime can’t break your product. Entitlement checks, by contrast, deny when they can’t produce an affirmative grant, so an outage never leaks a paid feature.
  • Money-safe decisions belong on your server. A browser check can be tampered with. For anything that costs money or invites abuse, re-run the check server-side before you grant access.

The SDK gives you three ways to act on an entitlement. They sound alike; they are not the same.

ToolKindRunsUse it for
<Gate>React componentClient (UI)Show/hide a subtree and render an upsell when denied
rt.gate(action, fn)Imperative verbClientRun fn only if entitled; get the entitlement back to open a paywall
rt.can(handle) · useCan · checkEntitlementCheckClient or serverAsk “is this allowed?” and get the full result — the one you re-run server-side

<Gate> and gate() are convenience wrappers around a can()/checkEntitlement() call. All three run in the browser and are fail-closed — a check that can’t affirm a grant denies. The check itself — checkEntitlement / can — is also what you run on your server (via the server SDK or your own logic) when the decision has to be trusted.

ActionClient check enough?
Show or hide a button, badge, or menu item✅ Yes
Render an upgrade prompt or paywall✅ Yes
Branch UI copy or messaging✅ Yes
Start a paid export, consume credits, provision a seat❌ Re-check on your server
Anything a user could profit from by faking❌ Re-check on your server

The rule: the client check drives the UI; your server makes the money-safe decision. They use the same entitlement — you’re just not trusting the browser for the part that matters.

Use the client check for a fast, friendly UX:

import { Gate } from '@revturbine/sdk';
// Client: instant show/hide + upsell when denied. Fails closed.
<Gate id="editor_export" can="data_export">
<ExportButton />
</Gate>

Then verify again on your server before doing the paid work:

// Server: the trusted decision. Re-run the same entitlement check
// (via the RevTurbine server SDK, or your own logic) before granting.
async function handleExport(req, res) {
const entitled = await userIsEntitled(req.userId, 'data_export'); // your server check
if (!entitled) return res.status(402).json({ error: 'upgrade_required' });
await runExport(req);
}

The client <Gate> keeps the UI honest and fast; the server check keeps the revenue safe. If RevTurbine is down, the client denies (the gated UI stays gated rather than leaking), and your server check is still the backstop for the billing-critical step.