Skip to main content
Seat-based pricing lets you sell products where one person buys seats and assigns them to their team. Each seat holder gets their own benefits — license keys, Discord roles, file downloads, or anything else you offer.
Seat-based pricing is currently in private beta. Contact our support team to enable it for your organization.

Understanding the model

Before writing any code, there are three entities you need to understand. They change how you think about every API call, webhook, and portal flow.

Customer, Member, and CustomerSeat

With standard Polar products, one person buys and one person uses — they’re the same person. With seat-based products, buying and using are separate concerns, modeled through three distinct entities:
  • A Customer is the billing entity — who pays. They own subscriptions, orders, and payment methods. On first seat-based purchase, the customer is permanently upgraded to type: "team", which enables members and team management.
  • A Member is a person under a customer — who uses. Each member has their own email, role (owner, billing_manager, or member), and receives benefit grants independently. The person who purchases the products is created as an owner member.
  • A CustomerSeat is the link between a product and a member. It tracks assignment status (pending, claimed, revoked), holds the invitation token, and carries optional metadata.
Customer (Jane — billing manager, type: "team")
  ├── Member: Jane (role: owner)          → manages team, no seat and no product benefits
  ├── Member: Alice (role: member)        → gets benefits via seat
  └── Member: Bob (role: member)          → gets benefits via seat

Subscription (Team Pro — 3 seats)
  ├── CustomerSeat → Alice (claimed)      → benefits granted
  ├── CustomerSeat → Bob (claimed)        → benefits granted
  └── CustomerSeat → unassigned           → available

Why this separation matters

This three-entity model gives you flexibility that a simple “buyer = user” model can’t:
  • Members persist across products. The same member can hold seats from different subscriptions or orders under the same customer.
  • Roles control portal access. Owners and billing managers see full team management. Regular members see only their own benefits.
  • Benefits track to people, not purchases. Always use grant.member to identify who has access — grant.customer_id is always the billing entity, not the end user.

What this means for your integration

  • Benefit grants reference a member, not just a customer. Always use grant.member to identify who received the benefit.
  • Webhooks include a member object on seat and grant events. Use it instead of customer_id to identify the end user.
  • Benefits are not granted at purchase time. Seats must be assigned and claimed before benefits are granted.
Purchase → Assign seats → Members claim → Benefits granted

Prerequisites

  • Polar organization with seat-based pricing enabled
  • Polar SDK installed (npm install @polar-sh/sdk or pip install polar-sdk) — make sure it’s the latest version
  • Basic understanding of Polar products and subscriptions

Implementation

This guide covers both subscription and one-time purchase seat-based products. The flow is the same — the only difference is in scaling and billing.

Step 1: Create a seat-based product

1

Navigate to Products

In the Polar dashboard, go to Products and click Create Product.
2

Configure pricing

Under Pricing:
  • Product type: Subscription (recurring) or One-time (perpetual licenses)
  • Pricing type: Seat-based
  • Min seats: 1 (or your minimum team size)
Define volume-based tiers:
TierMax SeatsPrice per Seat
14$10/month
29$9/month
3Unlimited$8/month
3

Add benefits

Configure benefits that seat holders will receive (license keys, file downloads, Discord roles, etc.). Remember: benefits are granted on seat claim, not at purchase.

Step 2: Checkout

const checkout = await polar.checkouts.create({
  products: ["prod_123"],
  seats: 5,
  success_url: "https://your-app.com/success",
  customer_email: "billing@company.com"
});

// Redirect to checkout.url
The checkout automatically calculates pricing based on your tiers.

Step 3: Seat management

After purchase, the billing manager can assign and manage seats directly from the Customer Portal — no custom UI required. The portal provides:
  • Assign seats to team members by email
  • Revoke seats to remove access and free up seats for reassignment
  • Resend invitations for pending seats
  • Adjust seat count on subscriptions (add or reduce)
Polar automatically sends invitation emails to assigned team members with a secure claim link. Once a member claims their seat, benefits are granted immediately.
Invitation emails are sent automatically when seats are assigned. The claim flow is fully managed by Polar — members click the link, claim their seat, and get immediate access to benefits.

API-driven seat assignment

If you want to manage seat assignment programmatically (e.g., auto-assigning seats when users sign up), use the Customer Seats API:
const seat = await polar.customerSeats.assign({
  subscription_id: subscriptionId,
  email: "engineer@company.com",
  immediate_claim: true  // Skip invitation email, grant benefits immediately
});
Use immediate_claim: true when you manage your own user authentication and want to bypass the email invitation flow. Benefits are granted immediately without the member needing to click a claim link.

Step 4: Handle benefit grants

Listen for benefit webhooks to sync access in your system. Remember: use grant.member to identify the recipient, not grant.customer_id (which is the buyer).
app.post('/webhooks/polar', async (req, res) => {
  const event = req.body;

  if (event.type === 'benefit_grant.created') {
    const grant = event.data;
    const recipient = grant.member;
    await grantAccess(recipient.id, recipient.email, grant.benefit);
  }

  if (event.type === 'benefit_grant.revoked') {
    const grant = event.data;
    await revokeAccess(grant.member.id, grant.benefit);
  }

  res.sendStatus(200);
});

Step 5: Scale seats

Subscriptions — modify the seat count:
await polar.subscriptions.update({
  id: subscriptionId,
  seats: newTotal  // Cannot be less than currently assigned seats (pending + claimed)
});
You cannot reduce seats below the number of currently assigned seats (both pending and claimed). Revoke seats first before reducing the count.
One-time purchases — buy a new order:
const checkout = await polar.checkouts.create({
  products: [productId],
  seats: additionalSeats,
  success_url: "https://your-app.com/success"
});
// Each order has its own independent seat pool

Member sessions and portal

To give a member access to the customer portal, create a member session:
const session = await polar.memberSessions.create({
  member_id: "mem_789"
});
// Redirect to session.member_portal_url
  • Billing managers (owner/billing_manager role) see full team management — assigning seats, managing members, and viewing seat utilization.
  • Members see only their own benefits and account details.

Webhook events

EventWhenKey fields
order.paidCustomer completes purchasecustomer_id, product
subscription.updatedSeat count changescustomer_id, seats
customer_seat.assignedSeat assigned, invitation sentmember, email, status: "pending"
customer_seat.claimedMember claims their seatmember, status: "claimed"
benefit_grant.createdBenefit granted to membermember, customer_id (buyer)
customer_seat.revokedSeat revokedmember, status: "revoked"
benefit_grant.revokedBenefit removed from membermember, customer_id (buyer)
On subscription cancellation, each seat is revoked individually. A subscription with 5 seats and 3 benefits produces 1 + 5 + 15 = 21 webhook events. Make sure your handlers are idempotent.

Best practices

  • Use grant.member everywhere — not grant.customer_id — to identify who has access
  • Use seat metadata to store department, role, or cost center for your own tracking
  • Communicate clearly to billing managers that they won’t receive benefits directly

Troubleshooting

ProblemSolution
Cannot reduce seatsRevoke assigned seats first. You can’t go below the combined pending + claimed count.
Claim link expiredTokens expire after 24 hours. Resend via the portal or API: polar.customerSeats.resend({ seat_id })

Subscriptions vs one-time purchases

SubscriptionsOne-Time Purchases
PaymentRecurring (monthly/yearly)Single payment
Seat durationWhile subscribedPerpetual
Adding seatsModify subscriptionPurchase new order
BenefitsWhile subscription activeForever after claim

Limitations

  • Seats must be assigned individually (use API for bulk)
  • Claim links expire after 24 hours
  • Maximum 1,000 seats per subscription
  • Metadata limited to 10 keys and 1KB per seat

Next steps