Skip to main content
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.

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

IndustryExamplePricing Model
Team CollaborationSlack, Notion, AsanaPer active user/month
Developer ToolsGitHub, GitLab, JiraPer seat/month
CRM SoftwareSalesforce, HubSpotPer user license
Design ToolsFigma, CanvaPer editor seat
Security Software1Password, OktaPer user/month
Video ConferencingZoom, TeamsPer 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

ComponentPurposeExample
Base ProductCore subscription with included seats”Team Plan - $99/month (5 seats included)“
Seat Add-onPer-seat charge for additional users”Extra Seat - $15/month each”
QuantityNumber of additional seats purchased10 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:
1

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
2

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)
3

Consider Billing Frequency

Align seat pricing with your billing cycle:
  • Monthly subscriptions → monthly seat charges
  • Annual subscriptions → annual seat charges (often discounted)

Step 2: Create the Seat Add-on

In your Dodo Payments dashboard:
  1. Navigate to ProductsAdd-Ons
  2. Click Create Add-On
  3. Configure the add-on:
FieldValueNotes
Name”Additional Seat” or “Team Member”Clear, user-friendly name
Description”Add another team member to your workspace”Explain what customers get
PriceYour per-seat pricee.g., $10.00
CurrencyMatch your base productMust be the same currency
Tax CategorySame as base productEnsures consistent tax handling
Create descriptive add-on names that make sense on invoices. “Additional Team Seat” is clearer than “Seat Add-on” for customers reviewing their bills.

Step 3: Create the Base Subscription

Create your subscription product:
  1. Navigate to ProductsCreate 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
Your subscription product now supports seat-based pricing. Customers can purchase any quantity of additional seats during checkout.

Managing Seats

Adding Seats to New Subscriptions

When creating a checkout session, specify the seat quantity:
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: '[email protected]' },
  return_url: 'https://yourapp.com/success'
});

Changing Seat Count on Existing Subscriptions

Use the Change Plan API to adjust seats:
// 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:
// 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:
// 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.

Proration Modes

ModeAdding SeatsRemoving Seats
prorated_immediatelyCharge for remaining days in cycleCredit for unused days
difference_immediatelyCharge full seat priceCredit applied to future renewals
full_immediatelyCharge full seat price, reset billing cycleNo credit

Proration Examples

Scenario: 15-day billing cycle remaining, adding 5 seats at $10/seat
Prorated charge = ($10 × 5 seats) × (15 days / 30 days)
                = $50 × 0.5
                = $25 immediate charge
Customer pays $25 now, then $50/month on renewal.

Preview Before Changing

Always preview proration before making changes:
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

EventWhen TriggeredUse Case
subscription.activeNew subscription activatedProvision initial seats
subscription.plan_changedSeats added/removedUpdate seat count in your app
subscription.renewedSubscription renewedConfirm seat count unchanged
subscription.cancelledSubscription cancelledDeprovision all seats

Webhook Handler Example

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

Strictly prevent adding users beyond the seat count.
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);
}

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.
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:
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

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
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
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
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