Hey @Akash!
The transferred_to field would be the App User ID that initiated the restore. I’d recommend trying to reproduce this and inspecting the debug logs to double check what the App User ID is at the time of restore.
Also, is there a way to hit call revenue cat API from the app to get the Transfer event details, as the webhook may be delayed and the backend needs to change the access level immediately.
You can always pull the subscription status for the current App User ID through the SDK or server-side by hitting the GET /subscribers endpoint to make sure the current customer has the appropriate access. I’m not sure the exact architecture of your system, but if you are not gating any server-side API calls based on subscription status it should be sufficient to use the subscription status from the SDK then rely on webhooks to keep your server-side status in-sync even if there’s some delay. If you do need to gate some content server-side and need a realtime subscription status there, you could ping your server after a successful restore and call GET /subscribers from your backend to do a server-to-server subscription status sync for the current user. Then, when/if the transfer webhook is delivered it would be used to remove access from the App User ID it was transferred from. There could still be some delay here, but at least the customer that should have access will receive it immediately.
Thanks @ryan.
I inspected the debug logs as you suggested. Here’s what I found.
The Transfer webhook is hitting our server even before I have called the ‘login’ function in the app. Hence the Transfer webhook only contains anonymous user id initiated by revenuecat in the “transferred_to” field. And since the webhook contains an anonymous user id which is not identified at my server end, I am failing to transfer the purchase.
These are the different approaches with which I am thinking I can solve the issue -
- To be able to configure Revenuecat with my own user id initially itself by using the following statement - Purchases.configure(withAPIKey: "my_api_key", appUserID: "my_app_user_id"). The app user id is fetched from the server through an API call after the initial load. Hence I cannot configure it in the Appdelegate file. For me to add this snippet in a different file, I need to ensure that configure function is only triggered once. Is there a way to check if Revenuecat has already been configured, so that I can prevent configure function from triggering more than once.
- To store Transfer event in my database where there are anonymous user id’s. And when I call the api to fetch the user id, I would pass the anonymous user id as well along with it. I would then check if there are any pending transfers pertaining to this anonymous user and then make the transfer in my backend. For this I would need to know the anonymous user generated by revenuecat at the time of configuration. Is there a function with which I can know the anonymous user generated by revenuecat?
Approach 1 is easier to implement than approach 2. Please suggest.
Hello,
We are facing the exact same problem since a few days (noticed on Android at least, we are using the react-native SDK).
Our server receives TRANSFER events via RC webhook which contain an anonymous userId in the "transferred_to" field. Which breaks our payment logic on the server side because new subscriptions and renewals are now linked to the anonymous user making impossible for us to sync the server with Revenue Cat as the real userId is no more set as "app_user_id" nor part of the event aliases.
I am even more confused because our code explicitly call the "logIn" method before each call to an SDK method via this code snippet:
// To avoid any confusion about the ownership of the subscription, identify again the user at the moment of making purchase
const revenueCatUserId = Purchases.getAppUserID();
if (revenueCatUserId !== myUserId) {
await Purchases.logIn(myUserId);
// Add a short delay to increase chances that appUserId has been taken into account by RevenueCat SDK
await setDelay(2000);
}
Hey @Akash! Since in-app purchases are done through the Apple or Google account and not your app account, there is always a chance that a purchase can come through without your own identifier set - which is why RevenueCat assigns an anonymous App User ID to everyone on first app launch.
There’s things you can do to limit the occurrence of this, which it sounds like you’re handling, but to build a really robust system you should be prepared handle “anonymous” purchases.
Maybe the customer logged out then purchased through the Apple subscription management page, or maybe they uninstalled the app and when they reinstalled a purchase was sitting on the payment queue. I don’t think there’s a way to handle every scenario perfectly.
When thinking about the best server-side approach of handling these cases, maybe you can share a bit more about what the server-side requirements are and how webhooks play a role in that?
In the apps I work on, I rely on the SDK for all of the Entitlement access in-app so I can use the subscription status from RevenueCat directly so don’t need to worry about “anonymous purchases” through the webhooks. I do use webhooks to keep a subscription status on my server which I use for some in-app messaging. I basically ignore “anonymous purchases” on the webhook side and hope the customer is logged in for the next renewal and it would get captured then. Since the webhooks don’t power critical business logic this approach is fine for me.
Hi @ryan.
Here is an explanation of user management in my app -
After the app is downloaded and installed for the first time, we first initiate Revenuecat. Post that after navigating to the second screen (the first screen doesn’t require any API call), and API call is initiated to get auth token. After receiving the auth token, another API call is done to create a default user(where name, email is null in my backend). As soon as a user id is received, revenuecat is updated with this user id received from the backend. If the user then creates an account, a new user id is created with the user details (name and email) and any purchases are transferred to the new user in my backend. If the user subsequently logs out, logout API call is made and a new default user (name and email is nil) is returned. This new default user id is used to call Login function on revenuecat (which will ensure that logout of the previous user and login of the new default user is done at the same time). When Logout API is called in if there are any purchases for that user, it is transferred to the new default user.
If I can stop from anonymous user from assignment from revenuecat after installation, every other scenario to prevent from revenuecat anonymous user assignment is handled (i.e., logout, usage of app without signup) as I have default user assigned in the above cases.
So I want to be able to configure Revenuecat with my own user id initially itself by using the following statement - Purchases.configure(withAPIKey: "my_api_key", appUserID: "my_app_user_id"). But I cannot place this in Appdelegate file as I would need the first two API calls to be finished before I can configure Revenuecat with user ID from my server. For me to add this snippet in a different file, I need to ensure that configure function is only triggered once.
Is there a way to check if Revenuecat has already been configured, so that I can prevent configure function from triggering more than once?
For the question of how webhooks play a role for the server side logic -
The premium access is provided only from the server side. I am using webhook to ensure that the right user has the right premium access. The Transfer event is critical as changes the premium access from the old user id to new user id. Currently because the transfer event only contains revenuecat’s anonymous user id, I am unable to map the transfer to the new user.
Hey @Akash!
Sorry for the late reply here, there is an isConfigured
boolean on Purchases you can use to check if the RevenueCat SDK is already initialized.
https://github.com/RevenueCat/purchases-ios/blob/29dd2a8f1e0e0154c5fad9671f8cc8a49c81a7ec/Purchases/Public/Purchases.swift#L64