Understand entitlement tokens
The signed JWT that makes can() calls ~0ms — issuance, refresh, verification.
Checking "can this user do X" on every request would mean an API round trip per check. Instead, VibeMonetize issues a short-lived signed JWT — the entitlement token — that your server verifies locally, with no network call, via JWKS.
Shape
{
sub: endUserId,
appId: string,
plan: string | null,
features: string[],
meters: Record<meterSlug, { cap, used, remaining, resetsAt }>,
}ES256-signed, 5-minute TTL by default, aud pinned to the app ID so a token minted for one app can't be replayed against another.
Issuance and refresh (client)
<MonetizeProvider> mints the token on mount (POST /v1/entitlement-token) and refreshes it in the background at ~80% of its TTL. On network failure it retries with backoff and marks the context degraded — every client-side hook/component in @vibemonetize/react fails open in that state, since a billing outage should never lock users out of your UI.
Verification (server) — the real enforcement
import { createClient } from "@vibemonetize/server";
const monetize = createClient({ appId: process.env.VIBEMONETIZE_APP_ID! });
const claims = await monetize.requireEntitlement(token, "export");@vibemonetize/server verifies the signature, issuer, and audience against the app's JWKS (cached), then asserts the feature is granted. It fails closed: an invalid, expired, or under-entitled token throws a typed VibeMonetizeError (token_expired, unauthorized, payment_required, forbidden) rather than silently allowing access. This is the only enforcement of record — the client-side check is advisory.
Freshness trade-off
A revoked entitlement (e.g. a failed renewal) can stay valid client-side for up to the token's TTL. At VibeMonetize's price points, a ≤5-minute staleness window in exchange for ~0ms checks is the right trade — see PLAN.md §1 for the full latency/freshness/complexity comparison against per-check API calls and webhook-synced DBs.