Solved

Best Practice for Tracking Other Users' Entitlements/Subscription State

  • 18 February 2022
  • 10 replies
  • 391 views

Userlevel 1
Badge +4

I have a subscription tier in my iOS app. I understand how to access `purchaserInfo` to check if the current user has the entitlement associated with this tier. However, I would like to display a badge on all user icons for other subscribers in the app - a little like the ‘verified’ badges on a Twitter account image.

Clearly I need to store something server-side on my own user objects to track this, but I’m not sure what approach to take. Perhaps each user’s status should be saved to the server by my app as their entitlements change, and other instances use this saved user flag? But entitlements can change when the app isn’t being used, such as cancelled or expired subscriptions. My backend will have no knowledge of that change, so that use will continue to be displayed as a subscriber until and unless the launch the app again.

Any suggestions on a good approach for this? 

icon

Best answer by Miguel Carranza 21 February 2022, 04:17

View original

10 replies

Userlevel 2
Badge +6

Hi Ben!

 

I think the most straight forward approach to achieve this would be to leverage our webhooks. RevenueCat would send a notification to your server every time there is an event related to the subscription lifecycle (renewal, cancellation, expiration...). This way you should be able to keep the status of all your subscribers up to date server side, without the need for the subscriber to open the app

 

Userlevel 1
Badge +4

Thanks Miguel. I’m using Parse Server and I’m not much of a backend engineer, so I’m not sure that’s going to be an option for me. I don’t suppose there’s a way to fetch this same set of changes periodically? I do have access to what Parse calls cloud code, jobs I can run at a set time. 
 

Alternatively, can I hit the REST API from the client with the id of the user to get their current status? That might be useful in some circumstances such as viewing a users profile, but probably not for scrolling through a timeline and applying a badge, for example. 

Userlevel 2
Badge +6

It should be doable with Parse Server too! Cloud Functions should do the trick for handling incoming webhooks. I have not worked with Parse in years, but there are few tutorials online (like this one) that should help with the implementation.

 

I would not recommend fetching them periodically, because it will be more unreliable and you will be missing data. In any case, I would not fetch other users subscribers information from the client, since ideally you don’t expose other subscribers app_user_ids (we strongly suggest you use non-guessable ids)

Userlevel 1
Badge +4

Oh no kidding, I will look into handling webhooks then. Thank you!

Userlevel 1
Badge +4

Webhooks look like the solution here, but I do have a coupe of questions:

  • There are many different events that can be sent to me from RC (initial purchase, renewal, cancelation, etc). Each of these can have an immediate or delayed effect on the user’s entitlements. Do I need to consider each event type separately and poke at the dates etc to know if/when the user is currently a subscriber, and/or at which date that status will change? Or is there a way to simply check the current entitlements of the user?
  • If there *is* such a way, will I receive another event at the time the entitlement changes? For example, I might receive an event for a cancelled subscription, but that doesn’t mean the status should change yet. Will I receive another event once the entitlement expires (and assuming the user does not re-subscribe in the interim)? 
  • I wasn’t sure exactly what the section of the docs pasted below means. Is there are GET I can hit to pull down a list of all current subscribers? Is this implying I should pull that entire list on every event? Or just the user that the event is for? If the latter, does *that* include current entitlements? Perhaps this answers my two previous questions, assuming I do get another additional event when a cancelled subscription eventually expires?

Thanks for all your help!

Userlevel 1
Badge +4

Just posting some more progress, hoping I am on the right path. Recall that he end goal is to keep my  backend up to date with users’ subscription states.

It looks like the correct approach is to respond to any events from the RC webhook by immediately hitting the `GET` `/subscribers` endpoint for the `app_user_id` provided. This then returns the current state of the user according to RC.

So all that remains is:

  1. Does the webhook send an event for every change that affects entitlements? Including those that are action-trigerred (subscribing, cancelling), and those that happen later (renewal, expiry of cancelled subscription)?
  2. Which information from `GET` `/subscribers` should be used to determine subscriber status? It looks like `subscriber.entitlements.<entitlement_name>` is probably the place to start. Should I consider the `expires_date` the single source of truth, by comparing it to the current date? (Or maybe `grace_period_expires_date` if it exists?)
Userlevel 1
Badge +4

As I implemented this approach I realized it won’t work. Specifically, using `expires_date` will result in subscription gaps, I think. If I store and rely on `expires_date` to unlock functionality for an auto-renewing subscription, the webhook won’t tell me about the renewal until sometime after it has occurred. The user’s state will be incorrectly seen as unsubscribed for that period.

At least, that is what I am seeing in the Sandbox, where subscription periods are accelerated. My ‘yearly’ subscription renews every hour, and the webhook event isn’t firing before the renewal. Perhaps in a production environment I can expect the renewal (and its webhook event) to happen well in advance of the expiry date?

Kind of wish there was a bit more documentation around best practices in this area. I.e. how to sync up entitlement states and which property to use as the ‘truth’.

Userlevel 2
Badge +6

It will work in production. The stores try to renew before expiration date, so you should expect the webhook to show up before expiration. Sandbox is different given the shorter periods.

Userlevel 2
Badge +6

 

Does the webhook send an event for every change that affects entitlements?

I would say so! These are all the events we generate (including the ones you mentioned `RENEWAL`, `EXPIRATION`, `CANCELLATION`...)

 

Should I consider the `expires_date` the single source of truth, by comparing it to the current date? (Or maybe `grace_period_expires_date` if it exists?)

`expires_date` will always include the `grace_period_expires_date` if it’s present, so it’s ok to solely rely on `expires_date`

Userlevel 1
Badge +4

Thanks @Miguel Carranza you have been very helpful. For posterity, here is my current working solution.

  1. My server is the app’s source of truth for subscription state. It is kept updated via a webhook which receives events from RC and then looks up the user via `/subscriptions`. I store `expires_date` as the key piece of information.
  2. My app checks the user’s server-side `expires_date` to decide if a user is subscribed or not. The same property is used for the logged-in user and other users displayed in the app, to allow ‘pro’ badges on the profile images, for example. Even for the current user, I don’t want to use `purchaserInfo` because although it is well-cached, I can’t rely on this when scrolling a table view, etc since it can occasionally involve a network trip.
  3. I will user `purchaserInfo` in specific circumstances - when loading anything specifically related to purchasing or account management for the current user. For example when loading my paywall or when displaying a ‘subscription details’ screen. Here I can design for the latency.
  4. I found one case I had to handle. Immediately after the user subscribes (or restores), it will take an indeterminate moment before the webhook fires, their server-side status is updated, and can be refreshed from my server. I want to reflect their new status immediately in-app, so I store a local flag, which I call `hasSubscribedSinceLaunch`. I set this to `true` on a successful purchase, and it overrides any `expires_date` from the server. This grants subscriber access immediately on purchase for the life of the current launch. There is a slight risk that this launch lifetime could actually exceed the subscription period (e.g. after restoring a purchase that expires very soon), but the risk and impact of this is very small in my app. Also, this flag must be reset when the user changes.

Reply