Official walkthrough

From empty workspace to billed usage.

Five chapters, twenty-one steps — the dashboard first, then grantPlan, canAccess, consume, and Checkout. Tick steps as you go — your progress stays on this device.

01

Access rules

A plan table: plans across the top, feature keys down the side, an amount or on/off in each cell. UsageGate stores the Plan → Feature → Limit graph for Stripe and grantPlan — you never have to draw the edges.

01.1Open Access rules

Once your account and company exist, the first stop is the Access rules table. New workspaces already have Free, Starter, and Pro filled in — edit the numbers, or add your own plans and features.

  • Plans — Free, Starter, Pro (columns)
  • Features — ai_credits, export_pdf, seat_limit (rows)
  • Cells — type 10, leave empty, or tick on-off
Access rules · plan table

01.2Add or rename plans

Use + Plan for each commercial package. Paste a Stripe Price id on paid columns. Mark one unpaid plan as Default free so signup and cancel fall back there.

+ Plan · add a column

01.3Add the feature keys your code will ask about

Feature keys are what your backend calls. Pick boring, stable names — renaming later means updating every canAccess call.

your app
await gate.canAccess(userId, "generate_videos");
await gate.consume(userId, "ai_credits", 1);
+ Feature · add a row

01.4Fill what each plan includes, then Save

Type a number for credits or caps. Tick on-off features. An empty cell means that plan does not include the feature. Save publishes the draft. A collapsed Graph preview shows the generated Plan → Feature → Limit links if you want to inspect them.

Filled plan table · Saved
02

API key

Server-side secrets for the UsageGate SDK. We store only a SHA-256 hash and prefix — a secret key is shown exactly once and never leaves your server.

02.1Generate your secret key

Navigate to API keys, press Generate key, then copy the gk_live_… value from the shown-once panel. If you lose it, revoke and issue a new one.

.env
USAGEGATE_KEY=gk_live_…

// server only — never expose this to the browser
const gate = new GateClient(process.env.USAGEGATE_KEY!);
API keys · shown once
03

Stripe products

Plans become revenue when a Stripe price is attached. Create the product, copy the price ID, paste it on the plan column.

03.1Create a product

In the Stripe dashboard open Product catalog and press Create product.

Stripe · product catalog

03.2Name it and set the price

Name: Starter. Pricing: Recurring → Monthly → €10.00. Add product.

Add a product · €10.00 / month

03.3Open the product

The product page shows the default price row and the product ID.

Starter · active

03.4Copy the price ID

Open the price and copy the price_… identifier. That is the value UsageGate stores, not the product ID.

price_… identifier

03.5Paste it on the plan column

Back in Access rules, paste the price ID into the plan’s Stripe price id field and Save.

Plan column · Stripe price id
04

Webhook

Entitlements follow billing. Stripe tells UsageGate when an invoice is paid or a subscription changes; signatures are verified and replays ignored.

04.1Add an event destination

Stripe → Webhooks → Add destination. Listen to: Your account.

Stripe · webhooks

04.2Select four events

Search and check exactly these — nothing else is needed.

  • invoice.paid
  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
Selected events

04.3Choose Webhook endpoint

Destination type → Webhook endpoint → Continue.

Choose destination type

04.4Point it at UsageGate

Endpoint URL, then create the destination.

endpoint
https://www.usagegate.io/api/webhooks/stripe
Create an event destination

04.5Reveal the signing secret

On the destination details page, reveal and copy the whsec_… signing secret.

Signing secret · whsec_…

04.6Paste it back into UsageGate

Dashboard → Stripe. Paste the webhook signing secret, add the acct_… account hint, then Save secret. That is the loop closed: plans sell, invoices pay, entitlements refresh.

UsageGate · Stripe configuration
05

Your app

grantPlan, canAccess, consume — then Checkout for upgrade or trial. End users never see UsageGate — your server asks about them.

Prefer an agent? Copy for Cursor and paste the setup prompt into your product repo.

05.1Install the SDK

Server-side only. The key from chapter 02 stays in your env — never ship it to the browser.

your-app.ts
npm install @usagegate/sdk

import { GateClient } from "@usagegate/sdk";

const gate = new GateClient(process.env.USAGEGATE_KEY!);

05.2Assign Free on signup

Call grantPlan once with your existing user id. UsageGate fills every cell from the Free column and renews it each month. Free users never need a Stripe customer.

signup
await gate.grantPlan(userId, "plan_free");

05.3Gate the expensive route

Ask about a feature key, do the work, then burn. canAccess returns a boolean. Empty balance → 402. Paid refills come from Stripe, not another grant call.

generate.ts
const allowed = await gate.canAccess(userId, "ai_credits");
if (!allowed) {
  return Response.json({ error: "upgrade" }, { status: 402 });
}

const result = await doTheWork();

const burned = await gate.consume(userId, "ai_credits", 1);
if (!burned.success) {
  return Response.json({ error: "upgrade" }, { status: 402 });
}

05.4Upgrade: send them to Checkout

When a Free user clicks Upgrade, start a subscription on the Price you pasted on that plan column. Tag it with your user id. The webhook from chapter 04 fills the paid cells — do not call grantPlan or grant() after Checkout.

  • price_… = the Stripe price id on the paid column
  • end_user_id = the same id you used in grantPlan
  • Switching Price later (Starter → Pro) is the same path
app/api/checkout/route.ts
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(req: Request) {
  const { userId } = await req.json();

  const session = await stripe.checkout.sessions.create({
    mode: "subscription",
    line_items: [{ price: "price_pro", quantity: 1 }],
    success_url: `${process.env.APP_URL}/billing?ok=1`,
    cancel_url: `${process.env.APP_URL}/billing?canceled=1`,
    subscription_data: {
      metadata: { end_user_id: userId },
    },
  });

  return Response.json({ url: session.url });
}

05.5Trial: same Checkout, add trial days

There is no Trial column in UsageGate. Use Stripe’s trial on the paid Price. While status is trialing they get the paid cells. If they convert, they stay paid. If they cancel, they return to Free.

  • Same Price as the paid column — do not add a Trial plan
  • Do not use grant() for this
app/api/checkout/trial/route.ts
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(req: Request) {
  const { userId } = await req.json();

  const session = await stripe.checkout.sessions.create({
    mode: "subscription",
    line_items: [{ price: "price_pro", quantity: 1 }],
    success_url: `${process.env.APP_URL}/billing?ok=1`,
    cancel_url: `${process.env.APP_URL}/billing?canceled=1`,
    subscription_data: {
      trial_period_days: 7,
      metadata: { end_user_id: userId },
    },
  });

  return Response.json({ url: session.url });
}

Milestone complete — your app is gated.

Watch balances on the dashboard. Need a detail? The SDK and Stripe pages are reference, not another start.