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)
