What the member model brings
The member model introduces a split between who pays and who uses through three entities:- A Customer is the billing entity (who pays). They own subscriptions, orders, and payment methods. After migration, customers with seat-based products are upgraded to
type: "team". - A Member is a person under a customer (who uses). Each member has their own email and role (
owner,billing_manager, ormember), and receives benefit grants independently. - A CustomerSeat is the link between a subscription and a member. It tracks assignment status and carries metadata.
Customer. After migration, seat holders become Member records under the purchasing customer, and CustomerSeat links the subscription to each member.
For a complete overview of how these entities fit together, see the Seat-Based Pricing guide.
How the migration plays out: two phases
The migration plays out in two phases so you can adapt your integration before the breaking change lands.Phase 1: Preparation (non-breaking)
In this phase, Ruba createsMember records for all your existing seat holders and populates member_id on seats and benefit grants. Nothing breaks because customer_id on seats still points to the seat holder, and no customers are deleted.
This buys you time to start reading the new member and member_id fields and adapt your code before the breaking change.
Phase 2: Member model enabled (breaking)
In this phase, the member model is fully switched on.customer_id on seats and grants flips to point to the billing customer (the buyer), old seat-holder customer records are deleted, and all new operations lean on the member model.
This is the breaking change. If your code reads customer_id to identify seat holders, it will now get the buyer instead.
Breaking changes at a glance
These shifts happen in Phase 2 only:How to adapt your code
Adapt your code during Phase 1 so everything works when Phase 2 switches on. The
member field is available in both phases.1. Seats: identify holders by member, not customer_id
CallingGET /v1/customer-seats/{seat_id} or listing seats for a subscription returns:
Phase 1 (non-breaking, customer_id still points to Alice):
customer_id flipped to Jane):
- Use
seat.memberorseat.emailto identify who holds the seat. This works in both phases. seat.customer_emailstill resolves to the holder’s email, so it is safe to keep using.
2. Benefit grants: use grant.member for the recipient
CallingGET /v1/benefit-grants/{grant_id} or receiving a benefit_grant.created webhook returns:
Phase 1:
- Use
grant.memberto know who received the benefit. This is consistent across both phases. - License keys and downloads tied to this grant also transfer in Phase 2.
3. Replace stored seat-holder customer IDs
If you storedcust_alice or cust_bob in your database to track who has access, those IDs return 404 after Phase 2. During Phase 1, use the seat list endpoint to map old references to new member IDs:
4. Update webhook handlers
Seat webhooks:member.createdis emitted when a member joins the team (via seat assignment or customer creation)member.updatedis emitted when a member’s details changemember.deletedis emitted when a member is removed
5. Seat assignment
Existing assignment calls usingemail, customer_id, or external_customer_id keep working. Ruba resolves them to a member internally. However, the response shifts between phases:
Phase 1: seat.customer_id in the response still points to the seat holder.
seat.customer_id in the response points to the buyer, not the seat holder.
customer_id from the assignment response, switch to seat.member instead.
You can also assign by member ID directly:
6. Pass member_id when creating customer sessions
After migration, pass amember_id when creating customer sessions to scope the portal view to a specific member:
external_member_id as an alternative to member_id:
- Owners and billing managers see full team management, including assigning seats, managing members, and adjusting seat count.
- Regular members see only their own benefits and account details.
For team customers,
member_id is required. For individual customers, it is optional. When omitted, the owner member is used automatically.Migration checklist
During Phase 1 (do this now):- Adapt seat-handling code to use
seat.member/seat.emailinstead ofseat.customer_id - Adapt benefit grant code to use
grant.memberinstead ofgrant.customer_id - Replace any stored seat-holder customer IDs with member IDs
- Update webhook handlers to read
memberfrom seat and grant payloads - Pass
member_idwhen callingcustomerSessions.create()for portal access
- Use
member_idorexternal_member_idin seat assignment calls - Subscribe to
member.created,member.updated,member.deletedwebhooks
A real-world example
Imagine you sell a Team Pro plan. Jane (the manager) buys 3 seats and hands them out to Alice and Bob.Before migration (current state)
customer_id on the grant points to Alice directly.
After Phase 1: Preparation (non-breaking)
Members are created,member_id is populated on seats and grants, but customer_id still points to the seat holder. Your existing code keeps working.
seat.member and grant.member in your code. Both the old customer_id and the new member fields are available, so you can adapt your integration at your own pace.
After Phase 2: Member model enabled (breaking)
customer_id flips to the billing customer. Old seat-holder customers are deleted.
customer_id is cust_jane, the buyer. Your old webhook handler would grant access to Jane instead of Alice and Bob.