Skip to main content

Can someone please give me some clarification on the following scenario regarding anonymous users and the restorePurchases method. I’ve been chasing my tail on this for a week.

Here’s the scenario:

  • My app lets users use and subscribe to the app anonymously.
  • RevenueCat creates an anonymous App User ID, when the user first opens the app.
  • When the user subscribes, that anonymous ID is linked to the purchase.
  • Webhook events list this anonymous ID in both the app_user_id and original_app_user_id fields.

Now, let's assume the user gets a new phone:

  • They download and open the app, generating a new anonymous App User ID for the new device.
  • The user clicks "Restore Purchases," and I call restorePurchases() from the SDK.
  • The restorePurchases() method returns a CustomerInfo object containing an originalAppUserId field.

My questions are:

  1. What value will be in the originalAppUserId field of the CustomerInfo object after the restore?
    • The anonymous App User ID from the first device that originally purchased the subscription?
    • Or the new anonymous App User ID generated for the new device?
  2. What values will be in the both the app_user_id and original_app_user_id fields of future webhook events?
  3. If the user initiated a trial and then cancelled the trial before a payment processed, will the answers to questions 1 and 2 be the same?

Thank you for clarifying!

Extending on this ============================================

My app also let’s people create an account. So...

The docs say: 

The restorePurchases method should not be triggered programmatically, since it may cause OS level sign-in prompts to appear, and should only be called from some user interaction (e.g. tapping a "Restore" button.) If you are trying to restore a purchase programmatically, use syncPurchases instead.

So let’s consider two more scenarios:

Scenario 1 WITHOUT .login():

  • A user downloads my app.
  • RC creates an Anonymous User ID
  • Then the user subscribes, and creates an account.
  • I do NOT call the RevenueCat  .login() method.


Then the user gets a new phone:

  • The user signs into my app. 
  • RC generates a new anonymous ID.
  • I do NOT call the RevenueCat  .login() method.
  • I call syncPurchases because the docs say I shouldn’t call restorePurchases
  • Then I call getCustomerInfo()     

My questions are:

  1. Will the active subscription from the first device be connected to the second device after calling syncPurchases? It doesn’t appear so.
  2. What value will be in the originalAppUserId field of the CustomerInfo object? The anonymous ID from the first device or the second device?
  3. What values will be in the both the app_user_id and original_app_user_id fields of future webhook events?

 

Scenario 2 WITH .login():

  • A user downloads my app.
  • RC creates an Anonymous User ID
  • Then the user subscribes, and creates an account.
  • I DO call the RevenueCat  .login() method.

 

Then the user gets a new phone:

  • The user signs into my app.
  • RC generates a new anonymous ID
  • I DO call the RevenueCat  .login() method.
  • I call syncPurchases because the docs say I shouldn’t call restorePurchases
  • Then I call getCustomerInfo()     

My questions are:

  1. Will the active subscription from the first device be connected to the second device after calling syncPurchases? It think so because I called .login() , but I’m not sure.
  2. What value will be in the originalAppUserId field of the CustomerInfo object? The anonymous ID from the first device or the second device?
  3. What values will be in the both the app_user_id and original_app_user_id fields of future webhook events?


I’m sorry that this has become such a long question, but the documentation is a bit light on these concepts. 

Thank you for clarifying!


To help whoever answers this question at RevenueCat, I’ve explained what I “THINK” happens. Hopefully this saves you time.

Can you simply confirm or deny my assumptions and explain where I’m incorrect if I am?

 

Initial Scenario: Anonymous User Calling restorePurchases :

  1. originalAppUserId in CustomerInfo object = original anonymous ID.
  2. Future Webhook events: 
    • app_user_id = new anonymous ID
    • original_app_user_id = original anonymous ID.
  3. Same answers if trial was initiated and cancelled.


Account Scenario 1 (WITHOUT .login()) and syncPurchases:

  1. Subscription NOT connected to new device.
  2. originalAppUserId in CustomerInfo object = new anonymous ID.
  3. Future Webhook events: 
    • app_user_id = new anonymous ID
    • original_app_user_id = new anonymous ID.


Account Scenario 2 (WITH .login()) and syncPurchases:

  1. Subscription IS connected to new device.
  2. originalAppUserId in CustomerInfo object = original anonymous ID.
  3. Future Webhook events: 
    • app_user_id = logged in user ID
    • original_app_user_id = original anonymous ID.

 

Finally, for Account Scenario 2, is it necessary to call syncPurchases after the user gets a new phone? Or will calling .login() be enough to link the accounts and subscriptions?

Thanks,

Chris


Hi ​@Chris Thornham,

Thank you for the very detailed post! Let’s see if I can answer everything in one reply.

First of all, I want to put some definitions out here. `original_app_user_id` is the first app user id where we saw this specific user. If you don’t do any logIn or logOut it’ll be the same id as long as the user doesn’t delete the app. If they delete the app and re-install it again, a new anonymous id will be assigned and we’ll only know about their previous id if they do a restorePurchases or syncPurchases.

When that happens, we merge the users and `original_app_user_id` will be the first one and `app_user_id` the latest one.

The behavior of how the subscription works between original or latest user depends on your configuration, you can find all the information here including some examples that’ll help clarify some of your questions.

If you have a way to identify your users, then syncPurchases or restorePurchases is not needed, as soon as we identify the user we’ll provide them with the right access. You can find more information and things to consider here.

One thing to consider is when do you provide the custom app user id (or the logIn). If you do it after the configuration of the SDK, an anonymous alias will be provided. Once you do the logIn we’ll merge all the ids and aliases into a single customer.

 

Let me know if you have questions!


@joan-cardona 

Thanks for taking the time to get back to me. 

You’ve answered most of my questions, but I still need clarification on one scenario. I’ve read through the links you provided many times, and the answer is still not clear. 

 

So you know, the reason I’m so concerned about the app_user_id and original_app_user_id values in the webhook events is because I’m building a mobile/web platform that uses my backend as a single source of truth for access privileges.

 

I need to do this because I use Stripe payments and webhooks to manage the web app, and RC payments and webhooks to manage the mobile app. Therefore, I need a way to link both accounts on my backend.

You have confirmed the following:

When an anonymous user deletes and reinstalls the app OR gets a new phone, calling restorePurchases  means:

  1. When I call getCustomerInfo that the originalAppUserId value in the CustomerInfo object = original anonymous ID from the first device.
  2. Future Webhook events: 
    • app_user_id = new anonymous ID
    • original_app_user_id = original anonymous ID.
  3. Same answers if trial was initiated and cancelled.

 

Next:

I’m going to avoid the second scenario where I do NOT call .login() . So let’s take that off the table.

 

Finally: I still need to know what happens when an identified user who has an active subscription deletes and reinstalls the app OR gets a new phone and then Signs In to my app.

Here’s the flow:

  • The user (who has an active subscription) opens my app and I configure RC.
  • RC generates a new anonymous ID for the new device.
  • The user signs into my app.
  • I call the RevenueCat .login() method and pass it the Custom ID from my auth system.
  • I do NOT call syncPurchases because you said that is unnecessary.

Can you confirm:

  1. The subscription from the old device Will Be connected to new device.
  2. When I call getCustomerInfo that the originalAppUserId value in the CustomerInfo object = original anonymous ID from the first device.
  3. Future Webhook events will contain: 
    • app_user_id = logged in Custom User ID
    • original_app_user_id = original anonymous ID from the first device.

Thank you,

Chris


Hi Chris,

If you call logIn() from an anonymous user, they will be merged into a single user. When we merge a user, there will be only one App User ID within the original_app_user_id field in the webhook. You can still find the anonymous id in the field aliases. Find how they work here. This means that both the original and the app user id will be the same custom id - not the anonymous. We’ve recently expanded how logIn works when coming from an anonymous id with the table that you can find here.

Best,

 


@joan-cardona 
 

Thanks for explaining things. I really appreciate it.

 

That said, I wanted to share a couple of thoughts about the docs because they either show the opposite of what you just said, or could benefit from some additional clarity.

 

Here are three examples:

 

1)
Unclear Sample Webhooks.


You said:

This means that both the original and the app user id will be the same custom id - not the anonymous.

 

But the sample webhooks on your website would suggest otherwise. The “Initial Purchase” event seen here shows a custom App User Id in app_user_id
 while the original_app_user_id value is the original anonymous value also shown in the alias field.

 

2)
Ambiguity in original_app_user_id


The docs say:

When a merge of customers occurs, there will be only one App User ID within the original_app_user_id field.

 

But it’s not clear which App User ID will be in the original_app_user_id field. Is it the original anonymous ID? The custom ID? How could I know from reading that sentence?

 

3)

Missing Details in the TableThe table you referenced doesn’t explain what values show up in the app_user_id or original_app_user_id fields for webhooks or the CustomerInfo object. There’s no way to know what to expect from the table alone.

 

Just to clarify—I’m not trying to be difficult. I read the docs thoroughly before reaching out, and I’ve worked with plenty of SDKs before. This isn’t the first time I’ve done something like this, but I found these areas pretty confusing.

 

Maybe it’s just me, but I think adding a little more detail to these areas might really help.

 

Thanks again for your help,

 

Chris



 


Hi,

I've talked to the team and we did some test cases because as you mentioned it is not clear at all what those webhook fields mean. Also there are many edge cases that makes it work differently and we've also changed that behavior a couple of times already. We arrived to a definition for the 3 identity fields in webhooks that I think can clarify a lot of things:

  • original_app_user_id: This is the first id we've ever seen from this specific customer. 
  • app_user_id : This is the latest id we have from an specific customer.
  • alisases: It's an array containing all the known ids from an specific customer.

 
What does it mean in your case? As you correctly pointed out, original_app_user_id will be the anonymous id first created and app_user_id will be the id you've provided on logIn.
The webhook examples are also not valid since aliases don't contain all the ids, we are going to fix it. We are also going to change the text in the documentation because it's not clear.
 
Sorry for the confusion and for the error on my answer and please let me know if this clarifies it or if you have questions. 


@joan-cardona

There's another important omission in the documentation.

Consider this scenario:

  1. An anonymous customer subscribes on Device #1. RevenueCat assigns an anonymous ID to that device—let’s call it RCAnonIdOne.
  2. The customer gets a new device, and RevenueCat assigns a new anonymous ID—let’s call it RCAnonIdTwo.
  3. The customer mistakenly subscribes again on the new device.

Here’s the key issue:

  • Before they complete the purchase, the CustomerInfo object reports original_app_user_id as RCAnonIdOne.
  • After they complete the purchase (when purchasePackage() is called from the SDK), the CustomerInfo object updates, and original_app_user_id now becomes RCAnonIdTwo.

This means that calling purchasePackage() automatically syncs purchases with the App Stores and updates the CustomerInfo object, even if you do not explicitly call .restorePurchases(), .syncPurchases(), or .login().

I’ve carefully reviewed the documentation and couldn’t find this behavior explained anywhere. I just spent five hours debugging errors caused by this, and I believe it should be documented.


Hi ​@Chris Thornham,

When you say “The customer mistakenly subscribes again on the new device.” do you mean it goes through purchasing but then it acts as a restore since they already own it, right?

We do update the `CustomerInfo` once we know the purchase history of a customer, which as you correctly pointed out it happens as well on purchase. I’ll make sure to note it and add it somewhere in the docs.

Thank you!


@joan-cardona 

My testing has shown that the Apple App Store will block a payment if a subscription exists. This happens even if you don’t call syncPurchases() or restorePurchases(). This isn’t clear in the docs.

 

After reading the docs, I interpreted that a customer can purchase multiple subscriptions from the same store on a new device since that new device will have a new Anon User ID from RC. 


On a separate note, you said the following above:


As you correctly pointed out, original_app_user_id will be the anonymous id first created and app_user_id will be the id you've provided on logIn.

 

After days of testing, I cannot replicate that behavior. 

If I delete and reinstall the app, calling .login() does not update the original_app_user_id to the anonymous ID from the first device. Instead, it replaces it. I tried all of the options here, and nothing reproduced the behavior you described.

 

In all cases, either:

  1. The original_app_user_id was replaced with the new device’s ID, or
  2. The user lost their entitlements.

 

After a lot of trial and error, I found that calling .login() was the issue. If I avoid calling .login(), both syncPurchases() and restorePurchases() correctly update the original_app_user_id to the anonymous ID from the first device.

This approach provides a consistent, reliable user ID that I can connect to each customer.


Reply