> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Seat-Based Billing

> Implement per-user pricing for team software, SaaS products, and enterprise licenses with flexible seat management and proration.

<Info>
  Seat-based billing lets you charge customers based on the number of users, team members, or licenses they need. It's the standard pricing model for team collaboration tools, enterprise software, and B2B SaaS products.
</Info>

<CardGroup cols={2}>
  <Card title="Implementation Tutorial" icon="code" href="/developer-resources/seat-based-pricing">
    Step-by-step guide with code examples.
  </Card>

  <Card title="Add-ons Documentation" icon="puzzle" href="/features/addons">
    Learn about the add-on system that powers seat-based billing.
  </Card>

  <Card title="Subscription Management" icon="repeat" href="/features/subscription">
    Manage seat-based subscriptions and plan changes.
  </Card>

  <Card title="Webhooks" icon="bell" href="/developer-resources/webhooks/intents/subscription">
    Track seat changes with subscription webhooks.
  </Card>
</CardGroup>

***

## What is Seat-Based Billing?

Seat-based billing (also called per-user or per-seat pricing) charges customers based on the number of users who access your product. Instead of a flat fee, the price scales with team size.

### Common Use Cases

| Industry           | Example              | Pricing Model         |
| ------------------ | -------------------- | --------------------- |
| Team Collaboration | Slack, Notion, Asana | Per active user/month |
| Developer Tools    | GitHub, GitLab, Jira | Per seat/month        |
| CRM Software       | Salesforce, HubSpot  | Per user license      |
| Design Tools       | Figma, Canva         | Per editor seat       |
| Security Software  | 1Password, Okta      | Per user/month        |
| Video Conferencing | Zoom, Teams          | Per host license      |

### Benefits of Seat-Based Pricing

**For Your Business:**

* Revenue scales naturally as customers grow
* Predictable pricing customers can budget for
* Clear upgrade path from individual to team to enterprise
* Higher lifetime value as teams expand

**For Your Customers:**

* Pay only for what they use
* Easy to understand and forecast costs
* Flexibility to add/remove users as needed
* Fair pricing that matches team size

***

## How Seat-Based Billing Works in Dodo Payments

Dodo Payments implements seat-based billing using the **Add-ons** system. Here's how it works:

### Architecture Overview

A Team Pro subscription costs $99/month and includes 5 seats. If you have more than 5 users, you pay an additional $15/month for each extra seat.

For example, if your team needs 15 seats:

* Base Plan: \$99/month (includes 5 seats)
* Add-ons: 10 extra seats × $15/month = $150/month
* Total monthly cost: $99 + $150 = \$249 for 15 seats

### Key Components

| Component    | Purpose                               | Example                                     |
| ------------ | ------------------------------------- | ------------------------------------------- |
| Base Product | Core subscription with included seats | "Team Plan - \$99/month (5 seats included)" |
| Seat Add-on  | Per-seat charge for additional users  | "Extra Seat - \$15/month each"              |
| Quantity     | Number of additional seats purchased  | 10 extra seats                              |

***

## Pricing Strategies

Choose the seat-based pricing strategy that fits your business:

### Strategy 1: Base + Per-Seat Add-on

Include a set number of seats in the base plan, charge for additional seats.

**Example:**

```
Starter Plan: $49/month
├── Includes: 3 seats
├── Extra seats: $10/month each
└── 8 total seats = $49 + (5 × $10) = $99/month
```

**Best for:** Products where small teams can function with the base offering.

### Strategy 2: Pure Per-Seat Pricing

Charge a flat rate per seat with no base fee.

**Example:**

```
Per User: $12/month
├── 5 users = $60/month
├── 20 users = $240/month
└── 100 users = $1,200/month
```

**Implementation:** Set base plan price to \$0, use only the seat add-on.

**Best for:** Simple, transparent pricing; usage-based models.

### Strategy 3: Tiered Seat Pricing

Different base plans with different per-seat rates.

**Example:**

```
Starter: $0/month base + $15/seat
├── Lower features, higher per-seat cost

Professional: $99/month base + $10/seat
├── More features, lower per-seat cost

Enterprise: $499/month base + $7/seat
└── All features, volume discount on seats
```

**Implementation:** Create separate products for each tier with different add-on prices.

**Best for:** Encouraging upgrades to higher tiers; enterprise sales.

### Strategy 4: Seat Bundles

Sell seats in packs rather than individually.

**Example:**

```
5-Seat Pack: $50/month ($10/seat)
10-Seat Pack: $80/month ($8/seat)
25-Seat Pack: $175/month ($7/seat)
```

**Implementation:** Create multiple add-ons for different pack sizes.

**Best for:** Simplifying purchasing decisions; encouraging larger commitments.

***

## Setting Up Seat-Based Billing

### Step 1: Plan Your Pricing

Before implementation, define your pricing structure:

<Steps>
  <Step title="Define Base Plan">
    Decide what's included in the base subscription:

    * Base price (can be \$0 for pure per-seat)
    * Number of included seats
    * Features available at this tier
  </Step>

  <Step title="Set Seat Pricing">
    Determine the per-seat add-on cost:

    * Price per additional seat
    * Any volume discounts (via multiple add-ons)
    * Maximum seats allowed (if applicable)
  </Step>

  <Step title="Consider Billing Frequency">
    Align seat pricing with your billing cycle:

    * Monthly subscriptions → monthly seat charges
    * Annual subscriptions → annual seat charges (often discounted)
  </Step>
</Steps>

### Step 2: Create the Seat Add-on

In your Dodo Payments dashboard:

1. Navigate to **Products** → **Add-Ons**
2. Click **Create Add-On**
3. Configure the add-on:

| Field        | Value                                       | Notes                           |
| ------------ | ------------------------------------------- | ------------------------------- |
| Name         | "Additional Seat" or "Team Member"          | Clear, user-friendly name       |
| Description  | "Add another team member to your workspace" | Explain what customers get      |
| Price        | Your per-seat price                         | e.g., \$10.00                   |
| Currency     | Match your base product                     | Must be the same currency       |
| Tax Category | Same as base product                        | Ensures consistent tax handling |

<Tip>
  Create descriptive add-on names that make sense on invoices. "Additional Team Seat" is clearer than "Seat Add-on" for customers reviewing their bills.
</Tip>

### Step 3: Create the Base Subscription

Create your subscription product:

1. Navigate to **Products** → **Create Product**
2. Select **Subscription**
3. Configure pricing and details
4. In the **Add-Ons** section, attach your seat add-on

### Step 4: Attach Add-on to Product

Link the seat add-on to your subscription:

1. Edit your subscription product
2. Scroll to **Add-Ons** section
3. Click **Add Add-Ons**
4. Select your seat add-on
5. Save changes

<Check>
  Your subscription product now supports seat-based pricing. Customers can purchase any quantity of additional seats during checkout.
</Check>

***

## Managing Seats

### Adding Seats to New Subscriptions

When creating a checkout session, specify the seat quantity:

```typescript theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [{
    product_id: 'prod_team_plan',
    quantity: 1,
    addons: [{
      addon_id: 'addon_seat',
      quantity: 10  // 10 additional seats
    }]
  }],
  customer: { email: 'admin@company.com' },
  return_url: 'https://yourapp.com/success'
});
```

### Changing Seat Count on Existing Subscriptions

Use the Change Plan API to adjust seats:

```typescript theme={null}
// Add 5 more seats to existing subscription
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_team_plan',
  quantity: 1,
  proration_billing_mode: 'prorated_immediately',
  addons: [{
    addon_id: 'addon_seat',
    quantity: 15  // New total: 15 additional seats
  }]
});
```

### Removing Seats

To reduce seat count, specify the lower quantity:

```typescript theme={null}
// Reduce from 15 to 8 additional seats
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_team_plan',
  quantity: 1,
  proration_billing_mode: 'difference_immediately',
  addons: [{
    addon_id: 'addon_seat',
    quantity: 8  // Reduced to 8 additional seats
  }]
});
```

### Removing All Additional Seats

Pass an empty addons array to remove all add-ons:

```typescript theme={null}
// Remove all additional seats, keep only base plan seats
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_team_plan',
  quantity: 1,
  proration_billing_mode: 'difference_immediately',
  addons: []  // Removes all add-ons
});
```

***

## Proration for Seat Changes

When customers add or remove seats mid-cycle, proration determines how they're billed.

```mermaid theme={null}
sequenceDiagram
    participant A as Admin
    participant App as Your App
    participant D as Dodo Payments
    A->>App: Add/remove seats
    App->>D: Preview change
    D-->>App: Prorated charge preview
    App->>A: Show cost details
    A->>App: Confirm
    App->>D: changePlan (new seat count)
    D->>D: Prorate & charge/credit
    D-->>App: Updated subscription
    D->>App: Webhook: plan_changed
    App->>A: Seats updated
```

### Proration Modes

| Mode                     | Adding Seats                                | Removing Seats                    |
| ------------------------ | ------------------------------------------- | --------------------------------- |
| `prorated_immediately`   | Charge for remaining days in cycle          | Credit for unused days            |
| `difference_immediately` | Charge full seat price                      | Credit applied to future renewals |
| `full_immediately`       | Charge full seat price, reset billing cycle | No credit                         |

### Proration Examples

**Scenario: 15-day billing cycle remaining, adding 5 seats at \$10/seat**

<Tabs>
  <Tab title="prorated_immediately">
    ```
    Prorated charge = ($10 × 5 seats) × (15 days / 30 days)
                    = $50 × 0.5
                    = $25 immediate charge
    ```

    Customer pays $25 now, then $50/month on renewal.
  </Tab>

  <Tab title="difference_immediately">
    ```
    Immediate charge = $10 × 5 seats = $50
    ```

    Customer pays full \$50 now, regardless of cycle position.
  </Tab>

  <Tab title="full_immediately">
    ```
    Immediate charge = Full subscription + add-ons
    Billing cycle resets to today
    ```

    Customer pays full amount, new billing cycle starts.
  </Tab>
</Tabs>

**Scenario: Removing 3 seats mid-cycle with prorated\_immediately**

```
Current: Team Plan ($99/month) + 10 extra seats × $10/seat = $199/month
Change: Remove 3 seats (10 → 7 extra seats) on day 20 of 30-day cycle
Remaining: 10 days

Credit for removed seats:
  = ($10 × 3 seats) × (10 days / 30 days)
  = $30 × 0.333
  = $10.00 credit

→ $10.00 credit added to subscription
→ Next renewal: $99 + (7 × $10) = $169.00/month
→ Credit auto-applies: $169.00 − $10.00 = $159.00 on next invoice
```

<Tip>
  **Choosing a proration mode for seat changes**: Use `prorated_immediately` for fair day-based billing when teams frequently adjust seats. Use `difference_immediately` for simpler math that charges or credits the full seat price. See the [Proration Guide](/developer-resources/subscription-upgrade-downgrade#proration-modes) for detailed comparisons.
</Tip>

### Preview Before Changing

Always preview proration before making changes:

```typescript theme={null}
const preview = await client.subscriptions.previewChangePlan('sub_123', {
  product_id: 'prod_team_plan',
  quantity: 1,
  proration_billing_mode: 'prorated_immediately',
  addons: [{ addon_id: 'addon_seat', quantity: 20 }]
});

console.log('Immediate charge:', preview.immediate_charge.summary);
// Show customer: "Adding 5 seats will cost $25 today"
```

***

## Tracking Seats with Webhooks

Monitor seat changes by listening to subscription webhooks:

### Relevant Events

| Event                       | When Triggered             | Use Case                      |
| --------------------------- | -------------------------- | ----------------------------- |
| `subscription.active`       | New subscription activated | Provision initial seats       |
| `subscription.plan_changed` | Seats added/removed        | Update seat count in your app |
| `subscription.renewed`      | Subscription renewed       | Confirm seat count unchanged  |
| `subscription.cancelled`    | Subscription cancelled     | Deprovision all seats         |

### Webhook Handler Example

```typescript theme={null}
app.post('/webhooks/dodo', async (req, res) => {
  const event = req.body;

  switch (event.type) {
    case 'subscription.active':
      // New subscription - provision seats
      const seats = calculateTotalSeats(event.data);
      await provisionSeats(event.data.customer_id, seats);
      break;

    case 'subscription.plan_changed':
      // Seats changed - update access
      const newSeats = calculateTotalSeats(event.data);
      await updateSeatCount(event.data.subscription_id, newSeats);
      break;

    case 'subscription.cancelled':
      // Subscription cancelled - deprovision
      await deprovisionAllSeats(event.data.subscription_id);
      break;
  }

  res.json({ received: true });
});

function calculateTotalSeats(subscriptionData) {
  const baseSeats = 5;  // Included in plan
  const addonSeats = subscriptionData.addons?.reduce(
    (total, addon) => total + addon.quantity, 0
  ) || 0;
  return baseSeats + addonSeats;
}
```

***

## Enforcing Seat Limits

Your application must enforce seat limits. Dodo Payments tracks billing, but you control access.

### Enforcement Strategies

<Tabs>
  <Tab title="Hard Limit">
    Strictly prevent adding users beyond the seat count.

    ```typescript theme={null}
    async function inviteUser(teamId: string, email: string) {
      const team = await getTeam(teamId);
      const subscription = await getSubscription(team.subscriptionId);
      const totalSeats = calculateTotalSeats(subscription);
      const usedSeats = await countTeamMembers(teamId);

      if (usedSeats >= totalSeats) {
        throw new Error('No seats available. Please upgrade your plan.');
      }

      await sendInvitation(teamId, email);
    }
    ```
  </Tab>

  <Tab title="Soft Limit with Warning">
    Allow exceeding with a warning and grace period.

    ```typescript theme={null}
    async function inviteUser(teamId: string, email: string) {
      const team = await getTeam(teamId);
      const { totalSeats, usedSeats } = await getSeatInfo(team);

      if (usedSeats >= totalSeats) {
        // Allow but flag for billing
        await flagOverage(teamId, usedSeats - totalSeats + 1);
        await notifyAdmin(team.adminEmail, 'You have exceeded your seat limit');
      }

      await sendInvitation(teamId, email);
    }
    ```
  </Tab>

  <Tab title="Auto-Upgrade">
    Automatically add seats when limit is reached.

    ```typescript theme={null}
    async function inviteUser(teamId: string, email: string) {
      const team = await getTeam(teamId);
      const { totalSeats, usedSeats, subscriptionId } = await getSeatInfo(team);

      if (usedSeats >= totalSeats) {
        // Automatically add a seat
        await client.subscriptions.changePlan(subscriptionId, {
          product_id: team.productId,
          quantity: 1,
          proration_billing_mode: 'prorated_immediately',
          addons: [{ addon_id: 'addon_seat', quantity: totalSeats - baseSeats + 1 }]
        });

        await notifyAdmin(team.adminEmail, 'A new seat was added to your plan');
      }

      await sendInvitation(teamId, email);
    }
    ```
  </Tab>
</Tabs>

***

## Advanced Patterns

### Different Seat Types

Offer different seat types with different pricing:

```
Full Seats: $20/month - Full access to all features
View-Only Seats: $5/month - Read-only access
Guest Seats: $0/month - Limited external collaborator access
```

**Implementation:** Create separate add-ons for each seat type.

```typescript theme={null}
const session = await client.checkoutSessions.create({
  product_cart: [{
    product_id: 'prod_team_plan',
    quantity: 1,
    addons: [
      { addon_id: 'addon_full_seat', quantity: 10 },
      { addon_id: 'addon_viewer_seat', quantity: 25 },
      { addon_id: 'addon_guest_seat', quantity: 50 }
    ]
  }]
});
```

### Annual Seat Discounts

Offer discounted annual seat pricing:

```
Monthly: $15/seat/month
Annual: $12/seat/month (20% savings)
```

**Implementation:** Create separate products for monthly and annual plans with different add-on prices.

### Minimum Seat Requirements

Require a minimum number of seats for certain plans:

```typescript theme={null}
async function validateSeatCount(planId: string, seatCount: number) {
  const minimums = {
    'prod_starter': 1,
    'prod_team': 5,
    'prod_enterprise': 25
  };

  if (seatCount < minimums[planId]) {
    throw new Error(`${planId} requires at least ${minimums[planId]} seats`);
  }
}
```

***

## Best Practices

### Pricing Best Practices

* **Clear Communication**: Show per-seat pricing prominently on your pricing page
* **Included Seats**: Consider including a few seats in the base price to reduce friction
* **Volume Discounts**: Offer lower per-seat rates for larger teams to win enterprise deals
* **Annual Incentives**: Discount annual plans to improve cash flow and retention

### Technical Best Practices

* **Cache Seat Counts**: Cache subscription seat counts locally to avoid API calls on every request
* **Sync Regularly**: Periodically sync your local seat count with Dodo Payments via API
* **Handle Failures**: If a seat change fails, show clear error messages and retry options
* **Audit Trail**: Log all seat changes for billing disputes and compliance

### User Experience Best Practices

* **Real-Time Feedback**: Show immediate cost impact when adjusting seats
* **Confirmation Steps**: Require confirmation before billing changes
* **Proration Transparency**: Clearly explain prorated charges before applying
* **Easy Downgrades**: Don't make it difficult to reduce seats (it builds trust)

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Seat count mismatch between app and billing">
    **Symptom**: Your app shows a different seat count than the subscription.

    **Causes**:

    * Webhook not received or processed
    * Race condition during seat change
    * Cached data not updated

    **Solutions**:

    1. Implement webhook handlers for `subscription.plan_changed`
    2. Add a "Sync with billing" button that fetches current subscription
    3. Set cache TTL to ensure regular refresh
  </Accordion>

  <Accordion title="Proration charges unexpected">
    **Symptom**: Customer confused by mid-cycle charge amount.

    **Causes**:

    * Proration mode not communicated clearly
    * Customer didn't see preview before confirming

    **Solutions**:

    1. Always use `previewChangePlan` before making changes
    2. Show clear breakdown: "Adding X seats = \$Y today (prorated for Z days)"
    3. Document your proration policy in help center
  </Accordion>

  <Accordion title="Add-on not appearing in checkout">
    **Symptom**: Seat add-on not available during checkout.

    **Causes**:

    * Add-on not attached to product
    * Add-on archived or deleted
    * Currency mismatch between product and add-on

    **Solutions**:

    1. Verify add-on is attached in product settings
    2. Check add-on status in Add-Ons dashboard
    3. Ensure currencies match exactly
  </Accordion>

  <Accordion title="Cannot reduce seats below current usage">
    **Symptom**: Customer wants to reduce seats but has users assigned.

    **Solutions**:

    1. Show which users must be removed before reducing seats
    2. Implement a workflow: Remove users → Reduce seats
    3. Consider a grace period before enforcing seat reduction
  </Accordion>
</AccordionGroup>

***

## Related Documentation

<CardGroup cols={2}>
  <Card title="Seat-Based Pricing Tutorial" icon="code" href="/developer-resources/seat-based-pricing">
    Complete implementation guide with code.
  </Card>

  <Card title="Add-ons" icon="puzzle" href="/features/addons">
    Understand the add-on system in depth.
  </Card>

  <Card title="Plan Changes & Proration" icon="arrows-rotate" href="/developer-resources/subscription-upgrade-downgrade">
    Handle subscription modifications.
  </Card>

  <Card title="Subscription Webhooks" icon="bell" href="/developer-resources/webhooks/intents/subscription">
    Track subscription events.
  </Card>
</CardGroup>
