Leveraging Stripe To Manage Your SaaS Entitlements
If you have a software-as-a-service product, you likely have multiple subscription plans with different feature sets for your customers to choose from. For example, our Labs project, Kind Kiosk, has the following subscription plans:
Until recently, your back-end implementation was responsible for managing entitlements for a customer’s subscribed plan. It can get complex to keep your back-end in sync with the customer’s subscription throughout its lifecycle as recurring payments may fail or the plan is upgraded, downgraded, or canceled.
Amongst many of the larger features announced at Sessions, Stripe quietly released a new Entitlements API. This API lets you create features which can then be assigned to products. As of writing this post, features can only be created using Stripe’s API and not through the Stripe Dashboard.
Continuing to use the example plans above, you’ll have the corresponding products in Stripe:
For customers subscribed to our “Growth” plan, they are allowed to create an unlimited amount of designations or causes. So let’s create a feature for this and assign it to our “Growth” product:
const feature = await stripe.entitlements.features.create({ name: 'Unlimited Causes/Designations', lookup_key: 'unlimited_designations', }); // Assign the newly created feature to a product const growthProductFeature = await stripe.products.createFeature( 'prod_growth', { entitlement_feature: feature.id, } );
You would repeat the above for all the features needed for your subscription plans. Now that all your features have been added to products, how can we use these to know which features a customer has access to? Stripe maintains a list of active entitlements for a customer when they subscribe to a plan and also keeps them up to date throughout the different events that may occur in the subscription lifecycle. Your app can be notified of changes to a customer’s entitlements by using Stripe Webhooks.
Whenever the customer’s active entitlements change (for example, when they subscribe, change, or cancel a plan), the entitlements.active_entitlement_summary.updated
webhook event will be sent. Here’s an example of the data your webhook endpoint will receive:
{ "object": { "object": "entitlements.active_entitlement_summary", "customer": "cus_MgWgA7lAKUOkm1", "entitlements": { "object": "list", "data": [ { "id": "ent_test_61QL65z2ila6DkHsA41DvCN66DEZt1Z2", "object": "entitlements.active_entitlement", "feature": "feat_test_61QL64Bts1fTy44Im41DvCN66DEZtA3s", "livemode": false, "lookup_key": "unlimited_designations" } ], "has_more": false, "url": "/v1/customer/cus_MgWgA7lAKUOkm1/entitlements" }, "livemode": false }, "previous_attributes": { "entitlements": { "data": [] } } }
You’ll likely need to retrieve a customer’s entitlements frequently, so Stripe recommends persisting the active entitlements in your database for performance reasons rather than fetching them on demand using the list API. These can be persisted when processing the webhook event. If you’re using Postgres, it could be as simple as having a user_id
column and an entitlements
column which is an array of the lookup_key
value from Stripe’s active entitlement object:
Now throughout your codebase, you can quickly retrieve your user’s entitlements and implement access control to features of your product. By placing the burden on Stripe to maintain entitlements, that should simplify your implementation and keep you focused on implementing your actual product.
Kishan is a senior software engineer at Echobind; want to work with him on a project? Write us an email at hi@echobind.com and we'll be in touch this week.