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, ormember), and receives benefit grants independently. The person who purchases the products is created as anownermember. - 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.
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.memberto identify who has access —grant.customer_idis always the billing entity, not the end user.
What this means for your integration
- Benefit grants reference a
member, not just acustomer. Always usegrant.memberto identify who received the benefit. - Webhooks include a
memberobject on seat and grant events. Use it instead ofcustomer_idto identify the end user. - Benefits are not granted at purchase time. Seats must be assigned and claimed before benefits are granted.
Prerequisites
- Polar organization with seat-based pricing enabled
- Polar SDK installed (
npm install @polar-sh/sdkorpip 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
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)
| Tier | Max Seats | Price per Seat |
|---|---|---|
| 1 | 4 | $10/month |
| 2 | 9 | $9/month |
| 3 | Unlimited | $8/month |
Step 2: Checkout
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)
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: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: usegrant.member to identify the recipient, not grant.customer_id (which is the buyer).
Step 5: Scale seats
Subscriptions — modify the seat count:Member sessions and portal
To give a member access to the customer portal, create a member session:- 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
| Event | When | Key fields |
|---|---|---|
order.paid | Customer completes purchase | customer_id, product |
subscription.updated | Seat count changes | customer_id, seats |
customer_seat.assigned | Seat assigned, invitation sent | member, email, status: "pending" |
customer_seat.claimed | Member claims their seat | member, status: "claimed" |
benefit_grant.created | Benefit granted to member | member, customer_id (buyer) |
customer_seat.revoked | Seat revoked | member, status: "revoked" |
benefit_grant.revoked | Benefit removed from member | member, 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.membereverywhere — notgrant.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
| Problem | Solution |
|---|---|
| Cannot reduce seats | Revoke assigned seats first. You can’t go below the combined pending + claimed count. |
| Claim link expired | Tokens expire after 24 hours. Resend via the portal or API: polar.customerSeats.resend({ seat_id }) |
Subscriptions vs one-time purchases
| Subscriptions | One-Time Purchases | |
|---|---|---|
| Payment | Recurring (monthly/yearly) | Single payment |
| Seat duration | While subscribed | Perpetual |
| Adding seats | Modify subscription | Purchase new order |
| Benefits | While subscription active | Forever 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

