Authdog Billing adds a subscription catalog to your environment: define plans and the features they grant, sell them through Stripe, and gate your application on the plan or feature a signed-in user holds. Subscriptions and entitlements live next to your users, so checking a plan is a local read, not a round trip to Stripe.

Billing runs on the environment's existing Stripe connection and supports one active subscription per subject.

## Prerequisites

Connect Stripe first, under **Authentication > Add-ons** in the [Authdog console](https://console.authdog.com). Billing reuses that connection — its secret key creates Products and Prices, and your checkout page initializes Stripe.js with its publishable key. There is no separate Billing credential.

Connect a Stripe test account to your development environment and the live account to production. Plans, features, and subscriptions never cross environments.

## Model

| Object | Meaning |
| --- | --- |
| **Feature** | A gateable capability, identified by a slug such as `advanced_reports`. |
| **Plan** | A purchasable bundle of features with a monthly price and an optional annual price. |
| **Subscription** | One subject's current plan, its period, and its Stripe status. |

A plan targets either a user or an organization (`forResource`), and slugs are unique within an environment. A feature's slug is what your application checks against, so treat it as frozen once it ships — renaming it revokes access for everyone holding it.

## Define plans

In the console, open **Billing > Plans** for the selected environment:

1. Add the features you want to gate on.
2. Create a plan: name, slug, description, and whether it is for a user or an organization.
3. Set the currency and the monthly amount, in cents. Add an annual amount only if you sell one — a plan can be month-only.
4. Attach the features the plan grants.
5. Choose whether the plan is publicly visible, whether it is the default, and its sort order in the pricing table.
6. Select **Sync to Stripe**.

Syncing creates or refreshes the plan's Stripe Product and its monthly and annual Prices. Stripe Prices are immutable, so changing an amount mints a new Price and leaves the old one in place — Stripe does not allow deleting a Price that has been used — and existing subscribers stay on the Price they bought until they change plans.

An unsynced plan has no Stripe Price and cannot be checked out.

## Client endpoints

Your application calls these on the Authdog identity origin, `https://identity.authdog.com`, or on your [custom domain](/docs/custom-domains) when one is configured.

| Endpoint | Auth | Purpose |
| --- | --- | --- |
| `GET /billing/:environmentId/plans` | None | Publicly visible plans, their features, and the Stripe publishable key. |
| `GET /billing/:environmentId/subscription` | Session | The caller's current subscription. |
| `POST /billing/:environmentId/subscriptions` | Session | Start a subscription; returns a client secret for payment confirmation. |
| `POST /billing/:environmentId/subscriptions/cancel` | Session | Cancel the caller's subscription. |

The plans endpoint is deliberately unauthenticated — a pricing page has to render before sign-in. It returns only plans marked publicly visible, and the publishable key, never a secret. Pass `?for=org` to list organization plans; the default is `user`.

Creating a subscription takes a `planId` and an optional `planPeriod` of `month` or `annual` (default `month`). The response carries a `clientSecret` your page confirms with Stripe Elements, so card details never touch Authdog.

```bash
curl -X POST "https://identity.authdog.com/billing/$ENVIRONMENT_ID/subscriptions" \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "planId": "pln_123", "planPeriod": "annual" }'
```

## Gate your application

The React SDK ships the pieces for a pricing page and for gating:

- `PricingTable` renders plans with a month/annual toggle and a per-plan call to action.
- `CheckoutButton` starts checkout for a plan and period.
- `Protect` renders its children only when the caller holds the given plan or feature.
- `hasBillingAccess` exposes the same check as a plain function, for use outside JSX.

```tsx
import { Protect } from "@authdog/react-elements"

<Protect
  subscription={subscription}
  activeFeatures={features}
  feature="advanced_reports"
  fallback={<UpgradePrompt />}
>
  <AdvancedReports />
</Protect>
```

`Protect` is a pure conditional render over state you already fetched — it makes no network call of its own. And like every client-side gate, it decides what the interface shows, not what the API allows: enforce entitlements again on your server before doing the paid work.

## Staying in sync with Stripe

Stripe's webhook is the source of truth for subscription state: it updates the stored status, current period end, and cancellation flag, and refreshes the entitlement cache for the plan's features. Feature slugs double as Stripe entitlement lookup keys, so a feature check reads the same cache that backs entitlement claims.

A subscription starts as `incomplete` and turns `active` once payment succeeds. Never grant access on the checkout response alone — wait for the subscription to report an active status.

Configure the Stripe webhook for the same account you connected, and verify a full purchase in a test environment before enabling live payments.

## Limits

- One active subscription per subject, per environment.
- Checkout accepts user plans only. Organization plans can be defined and listed with `?for=org`, but a subscription request for one is rejected.
- Amounts are integer cents in the plan's currency. A plan has a single currency.

## Related

- [Integrations](/docs/integrations)
- [Organizations](/docs/organizations)
- [Users](/docs/users)
- [JWT claims](/docs/jwt-claims)
