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.
The model, stated once
Section titled “The model, stated once”- 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.
Three tools, and where each runs
Section titled “Three tools, and where each runs”The SDK gives you three ways to act on an entitlement. They sound alike; they are not the same.
| Tool | Kind | Runs | Use it for |
|---|---|---|---|
<Gate> | React component | Client (UI) | Show/hide a subtree and render an upsell when denied |
rt.gate(action, fn) | Imperative verb | Client | Run fn only if entitled; get the entitlement back to open a paywall |
rt.can(handle) · useCan · checkEntitlement | Check | Client or server | Ask “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.
When do I need the server?
Section titled “When do I need the server?”| Action | Client 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.
In practice
Section titled “In practice”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.
Related
Section titled “Related”- Recommended API Path — when to reach for
can()vsgate()vs<Gate> - Server-Side Integration — running entitlement checks on your backend
- Entitlements — the entitlement model behind every check
- Error Handling — additive placements and fail-closed checks in detail