What do these RemoteConfigService settings do?

Hello, sorry in advance for asking multiple questions in one post; hopefully the docs can be updated with this information in the future.

I’m trying to understand the RemoteConfigService methods that one can call before calling FetchConfigs, i.e.:

  • SetAnalyticsUserID
  • SetConfigAssignmentHash
  • SetCustomUserID
  • SetEnvironmentID
  • SetPlayerID
  • SetPlayerIdentityToken
  • SetUserID

My questions are:

  • Are all of these methods optional? E.g., do I have to call SetAnalyticsUserID (and thus initialize the Analytics service) before calling FetchConfigs?
  • What is configAssignmentHash used for? Is it to improve performance when fetching configs multiple times per session? If so, is the hash meant to be saved at the start of a single session or persisted between sessions? Or is the hash just for auditing, so you can see what config values were returned to a specific player segment at a specific time? This post mentions using the hash for A/B testing, but I’m not sure what they mean by “the configuration” or “the config ID”.
  • What’s the difference between SetPlayerID(), SetUserID(), SetCustomUserID(), and SetAnalyticsUserID()? I assume that SetPlayerID expects to receive AuthenticationService.PlayerId and SetAnalyticsUserID expects IAnalyticsService.GetAnalyticsUserID, but what of the other two? SetUserID says “Sets userId to InstallationID identifier coming from core services”, but I see no installationID field on AuthenticationService. What is the benefit of calling these methods?
  • Is SetPlayerIdentityToken intended to be passed AuthenticationService.AccessToken? What’s the benefit of doing this?

Hi @Rabadash8820 , thanks for the questions, I will try to answer them and give a little bit of context where needed:

  1. All the methods you mentioned are indeed optional, and you do not need to invoke any of them before initialization of Analytics services and FetchConfigs. However, they had some internal purposes, and I will try to explain those, together with the complexity of many Ids we are sending during the RC request.

Remote Config makes a request to the backend in order to receive corresponding settings. Within the process, we send many Ids for different purposes (analytics, segmentation etc…) and all the methods from above are simply setting those ids for the request, even though in order for RC request to be valid only 2 params are required: projectId and userId.

Payload:
“projectId”: “35ff8616-5c15-45e8-94c6-3add4f02bacb”,
“userId”: “d4739198c1201433385ae222fa86ba01",
“playerId”: “oxbAhqECzRLkqhL9EUjlYHKXPQvs”,
“analyticsUserId”: “my-user-id-123",
“customUserId”: null,
“environmentId”: “11daa94d-0ffa-41e2-8579-f4acbbdb3987”,
Header:
Authorization Bearer eyJhbGciOiJSUzI1NiI0D0norSPCVpb1lDXAibEU5ZpQ8rj3BbQcWD0C6wolQ…
unity-installation-id d4739198c1201433385ae222fa86ba01
unity-player-id oxbAhqECzRLkqhL9EUjlYHKXPQvs

Inside payload:
projectId is retrieved via Application.cloudProjectId, and it is always available
userId is actually installationId coming from Core services, available immediately upon initialization
playerId comes from Auth and it is available upon user signin
analyticsUserId is coming from Core, available immediately, and it can be set from within the app, e.g

await UnityServices.InitializeAsync(options);```
**customUserId** is set by user within remoteConfig, used only for customer segmentation
**environmentId** comes from auth token and it is available upon user signin
Within Header:
Authorization uses **playerToken** as a **Bearer** token coming from auth access token, available upon user signin
**unity-installation-id** is same as installationId, coming from core
**unity-player-id** is same as playerId coming from auth
------------------------

Per example, if user does not set an environment, a default one will be used. However if user wants to force an environment, and knows an **environmentId;**
```RemoteConfigService.Instance.SetEnvironmentID("90593402-dccb-43a5-a09a-d8f17fffa7d4");```
will set the environment in the request, and settings from that environment will be returned.

Same goes for the rest of the rest of the methods, e.g:
```RemoteConfigService.Instance.SetCustomUserID("customer-1234"); ```

Methods like SetPlayerID, SetPlayerIdentityToken and SetUserID are not meant to be used by users, and they are set internally, but they are exposed for special cases, e.g it is useful for consoles to be able to set user id, as for those platforms we can not use certain analytics libraries needed for retrieving those ids.

That goes into your 3rd question, where SetPlayerID() and SetUserID() are meant to be used mostly internally, but SetCustomUserID() and SetAnalyticsUserID() can be used by the developer, and that is documented within Code Integration part in the docs, within the commented part
https://docs.unity3d.com/Packages/com.unity.remote-config@3.3/manual/CodeIntegration.html
and just to re-iterate, ``` SetCustomUserID("some-user-id"); ``` is used if you as a customer want to have your own unique id to track the user, which can be later utilized within the analytics.

SetAnalyticsUserId, however, has a slightly different purpose. It was used internally by old DDNA customers, who already had that id and wanted to use it for the new customers.

4. SetPlayerIdentityToken is only used internally to pass the token we get from Auth and core services, in order to track the player.

2. Remote Config Runtime includes the configAssignmentHash parameter in the request. This parameter can be used to ensure a persistent config in the response to later requests.

After making a request, currently, we get the following JSON block as a response:

{
“configs”: {
“settings”: {
“source”: “fromCampaignTest”,
“rotateY”: 12.0,
“rotSpeed”: 44.0,
“rotateZ”: -23.0,
“jsonCubeCustom”: {
“rotateX”: 20,
“color”: {
“r”: 0.2,
“g”: 0.2,
“b”: 0.3,
“a”: 1
}
},
“rotateX”: 55.0
}
},
“metadata”: {
“configAssignmentHash”: “4d1064c5198a26f073fe8301da3fc5ead35d20d1”,
“assignmentId”: “fda6c830-8f78-4cd1-b3a1-b00139e320bd”,
“environmentId”: “0ea1845e-320b-4004-98c5-b942e430acd3”
}
}


The metadata block returns 3 key-value pairs, with the following purpose:

- assignmentId gets created on each assignment event and it is used to track events and eventual user segmentation
- environmentId represents environment in which assignment happened
- configAssignmentHash is created upon assignment and presents a unique signature of that particular assignment.

Within the app, we can access configAssignmentHash from appConfig via:
``` RemoteConfigService.Instance.appConfig.configAssignmentHash; ```
Once we know the configAssignmentHash we want to use, it can be passed to the backend within the payload by using the SetConfigAssignmentHash() method:
```RemoteConfigService.Instance.SetConfigAssignmentHash("4d1064c5198a26f073fe8301da3fc5ead35d20d1"); ```
If configAssignmentHash is passed to the backend, the backend will return the config present at the time of creation of that particular configAssignmentHash.

This is particularly useful for the use-cases where per example, user paid not to see ads, and it should retrieve same settings (no ads) all the time no matter the level... etc...

TTL for the lifetime of configAssignmentHash is 56 hours. After that time, configAssignmentHash can be requested again, and TTL will reset to 56 hours again.

Caveats:
Usage of configAssignmentHash is very powerful, however, this power has some interesting side effects, e.g,:

- config values in the cache file might look unexpected, as instead of the config which would normally be passed, one sees a fixed config from the time configAssignmentHash was created
- Suppose the developer forgets that they are setting configAssignmentHash within the app. In that case, it might look like we got the wrong response from the backend as the returned config will stay fixed (sticky) even if a user should fall into a new campaign or if the environment has been changed.
------------------------

Sorry for this very long answer, RC was one of the first packages developed, and it had to cater many standards and packages that were developed after, which inadvertently introduced additional unwanted complexity, especially with many ids.
In its true nature, RC should be a simple key-value store, able to retrieve correct settings based on users request, utilizing environments and campaigns.

Hope this helps
1 Like

@vd_unity Fantastic answer, thank you for all the info! I think a lot of this historical context would be great to include in the docs. I’d be happy to help with this if the docs are open source.

Regarding service initialization, I’ve never seen that com.unity.services.core.analytics-user-id option before. Doesn’t that kinda create a chicken and egg problem, where you can’t initialize UnityServices until you have an analytics user ID, but you can’t initialize AnalyticsService to get that user ID until you’ve initialized UnityServices? Or are you saying that developers can provide their own custom value for that option, before initializing either class?

Given all this new info, would you mind telling me if the following high-level bootstrapping logic is correct? My game uses anonymous auth along with Analytics and RemoteConfig.

await UnityServices.InitializeAsync(new InitializationOptions()
    .SetEnvironmentName(environmentName)  // "development" or "production", e.g.
    .SetProfile("default")
);

// Retry a couple times if there are exceptions...
await AuthenticationService.Instance.SignInAnonymouslyAsync(options: null);
// If successful...
UnityServices.ExternalUserId = AuthenticationService.Instance.PlayerId;

// Show dialog to get user consents, if necessary

// In parallel...
// Call AnalyticsService.ProvideOptInConsent() as necessary
// Initialize 3rd party ad mediator with consent

RemoteConfigService remoteCfg = RemoteConfigService.Instance;
remoteCfg.SetAnalyticsUserID(AnalyticsService.Instance.GetAnalyticsUserID());
remoteCfg.SetEnvironmentID(environmentId);  // Copied from UGS Environments dashboard
remoteCfg.SetPlayerID(AuthenticationService.Instance.PlayerId);
remoteCfg.SetPlayerIdentityToken(AuthenticationService.Instance.AccessToken);
RuntimeConfig runtimeConfig = await remoteCfg.FetchConfigsAsync(new UserAttributes(), new AppAttributes());

Based on your last post (please correct me if I’m wrong)…

  • I don’t need to call RemoteConfigService.PlayerID or .PlayerIdentityToken, as these values can be derived from Authentication after signing in
  • I don’t need to call .SetEnvironmentId, as that will be looked up from the environment name I provided to UnityServices.InitializeAsync
  • I don’t need to call .SetAnalyticsUserID, as I am not a former deltaDNA customer. What will the analyticsUserId value be then?
  • Is the UnityServices.ExternalUserId assignment still necessary?
  • How can I overwrite the core services Profile to AuthenticationService.Instance.Profile after signing in?

Also, regarding the response metadata, I’m still confused by the concept of “assignment”. I don’t see a good definition of that term in the docs.

  • What is an “assignment event” and what can assignmentId be used for? Are you saying that every call to FetchConfigs(Async) (every request to the backend) will return a new assignmentId, like a sort of unique request ID?
  • I still don’t understand why one would manually set configAssignmentHash. How do you decide “which hash you want to use”? If the TTL is 56 hours, then I assume developers typically persist the hash between play sessions and use it in future FetchConfigs calls. But wouldn’t it be way less work just to use the values cached in RemoteConfigService.appConfig?
  • Does this all mean that if two responses have different configAssignmentHashes then they will also have different assignmentIds?

Thanks @Rabadash8820 !
Just to add a little bit of historical context, RC versions 1.x and 2.x did not use UGS services, such as Auth and Core, so no initialization of services was needed. Fetching was synchronous (without await)

ConfigManager.FetchConfigs(new userAttributes(), new appAttributes());

and we did not send playerId nor analyticsUserId to the backend.

Within version 3.x we embraced UGS services, so initialization was needed in order to get those ids and some other components, which were there to provide better analytics / tracking and to be used by some other services as well.

Regarding setting of analyticsUserId, there is newer api

var options = new InitializationOptions().SetAnalyticsUserId("test-user-id-12345");

if you want to set your own analyticsUserId. However, that was provided mainly for old Delta DNA users who wanted to force the old analyticsUserId, they tracked previously. If you don’t set it, RC will assign the value of installationId coming from core services. As a matter of fact, installationId gets assigned to analyticsUserId at first under the hood, and gets overwritten only if user assigns a new value in the initialization.

One more detail about .SetEnvironmentName(environmentName) vs .SetEnvironmentID(environmentId).
The first method comes from the services, and it obviously takes the name as a parameter, and core provides environmentIs which in turn calls the .SetEnvironmentID(environmentId) from RC anyways, and sets up the payload for RC request. They both do the same thing, but the one provided by RC is more direct, however, not everyone knows the environmentId, it is easier to remember the name :slight_smile: .
So for your use-case only one of those is enough, and if you know the environmentId, SetEnvironmentID(environmentId) is more direct. Of course, if you omit both methods, you will get the default environment.

Now lets try to address other questions in your post:

  • I don’t need to call RemoteConfigService.PlayerID or .PlayerIdentityToken, as these values can be derived from Authentication after signing in - CORRECT
  • I don’t need to call .SetEnvironmentId, as that will be looked up from the environment name I provided to UnityServices.InitializeAsync - CORRECT (it is enough to use only one api, either one from services or the one from RC)
  • I don’t need to call .SetAnalyticsUserID, as I am not a former deltaDNA customer. What will the analyticsUserId value be then? - CORRECT → installationId will be used
  • Is the UnityServices.ExternalUserId assignment still necessary?
    This is not necessary, as it is AFAIK bind to analyticsUserId. Here is the excerpt from the code for RCR initializer:
CoreConfig.analyticsUserId = IexternalUserId.UserId;
IexternalUserId.UserIdChanged += (id) => CoreConfig.analyticsUserId = id;

I believe that if ExternalUserId needs to be changed on the fly like this UnityServices.ExternalUserId = "TextUserID"; that would work only if you use Analytics package and only for new versions of Analytics from 4.4.1 see thread

  • How can I overwrite the core services Profile to AuthenticationService.Instance.Profile after signing in?
    This is the part that I don’t know, I would refer to Auth docs here , hopefully there will be some guidance, and if not, please let me know, I could contact someone form Auth team.

Regarding the metadata response:

  1. you are 100% correct, assignmentId is exactly that, a unique requestId, giving you the corresponding settings for your request.

  2. I will try make an example, as this issue is somewhat tricky;
    Let’s say for some reason, at one point in the game, we want to receive exact same settings, no matter what changes happen on the backend, new price, etc… You want to lock in your user into something. So, upon a successful request, we can read the configAssignmentHash from the metadata in response. Whenever we want to receive that same response, it is enough to pass that configAssignmentHash in the request.

  • This feature was required by some devs who e.g. wanted to return the same response as long as the player is in the tutorial, and once the user finishes the tutorial, configAssignmentHash was not passed anymore.

  • Similarly, in another example, once the user paid to see no ads, the response with “showNoAds”: true was sent back, and that configAssignmentHash was stored and used for all future requests.

In layman’s terms, configAssignmentHash is also called a “sticky” hash, and it was used whenever we wanted to make a response “sticky”. There might be an even better use for it (e.g. A/B test where you always want to return the same colored button for one group) but some game developers will probably have a better usage idea.

  1. Correct

Thanks once more for all this questions, it is also an insight for us how this all can be overwhelming and confusing, which was definitely not our intention. I hope this helps!

More great info; thank you very much. :slight_smile: Sounds like modern RemoteConfig initialization is actually a lot simpler than it appears from the various legacy methods lol. And that tutorial example for configAssignmentHash makes a fair bit of sense. Sounds like, unless I know I need a “sticky” config response for some reason, its safer not to store the value and just get the latest values at the start of each play session (and maybe again after X hours).

Also what sort of value does analyticsUserId have if I don’t manually override it with InitializationOptions.SetAnalyticsUserId()? Is it derived from SystemInfo.deviceUniqueIdentifier or something?

Thanks for linking the Authentication docs. Based on those, it sounds like changing the core profile after signing in is actually not supported:

Players must be signed out to switch the current profile. … Optionally, you can set the profile when initializing UnityServices. The AuthenticationService will use the value default if a profile is not provided.

So it sounds like the way to “change” the active profile is to sign out, call AuthenticationService.Instance.SwitchProfile, and then sign back in. This is assuming the relationship between a “profile” and a “player ID” is a one-to-one, which I’m not positive on. But then, how would you know which profile to switch to until someone has signed in? It also seems weird that one can set a profile on the core UnityServices class, but then have to change it through AuthenticationService.Instance. That just makes it feel like there are two different kinds of profiles…maybe there are?

1 Like

Thanks again @Rabadash8820 !

I am also not sure on how the core gets the installationId, but you are most probably right, as SystemInfo.deviceUniqueIdentifier looks like a best candidate. One of the things to help me understand is that obviously, installationId refers to a device, and playerId to a player, which is used in corresponding contexts.
E.g. Economy package bases everything on playerId, as it is the person who needs be referred when charging currency, while installationId makes more sense in the context of how many unique devices was involved in the game, and nowadays it is mostly one person per device anyway.

Thanks for digging more in the Auth services. I am not sure if I know how to answer your question, but I believe that profile and playerId indeed are one to one. While testing RC, I tried many times to sign anonymously,

await AuthenticationService.Instance.SignInAnonymouslyAsync();

and I always got the same playerId, for each session, even tough the sign in I used was anonymous.

For the last part, I am probably not the right person regarding knowing of inner workings of
UnityServices and AuthenticationService, but I know for the fact that e.g. Core and Auth packages work together, as we are using some Core components which become available after some trigger in Auth. Good example for it is the playerId component which is accessible immediately upon initialization, but gets the value only upon user signing in.

@vd_unity Gotcha, the distinction between a “device identifier” and a “player identifier” that works across devices definitely makes sense. And yeah, I guess I’m kinda going beyond the scope of this forum area with all these Auth questions…I do appreciate the info you’ve provided though.

I’m starting to apply our conclusions in my code and had a couple more questions:

  • Out of curiosity, if you wanted to set a custom user ID nowadays, would you use RemoteConfigService.SetCustomUserID or InitializationOptions.SetAnalyticsUserId (or both)? Is the expectation that teams only set a custom user ID when they are not using Unity Authentication?
  • I’m still a bit confused by UnityServices.ExternalUserId. In your code snippet above, you show CoreConfig.analyticsUserId being initialized to IexternalUserId.UserId and then updated whenever IexternalUserId.UserIdChanged is invoked. Based on that, it looks like analyticsUserId is always equal to ExternalUserId. However, you also said initially that analyticsUserId is immediately available from Core and can be initialized via InitializationOptions.SetAnalyticsUserId(). So is it that UnityServices.ExternalUserId is used by all services, while RemoteConfigService.SetAnalyticsUserId only affects RC, and that’s why the latter is not really recommended anymore (other than for old deltaDNA customers)?
  • Either way, I still can’t tell if ExternalUserId is automatically updated by AuthenticationService.SignInAnonymouslyAsync or if I need to manually reset it in an AuthenticationService.SignedIn event handler. The fact that the Analytics package recommends setting it in the thread you linked would suggest that we do need to explicitly reset it, but idk.

Hi @Rabadash8820 ,
those are some great questions and I will try to answer to the best of my knowledge, as I am bit more versed in RC issues than the nuances regarding the rest of the services. I might bring someone more knowledgeable to discussion regarding that.

  1. RemoteConfigService.SetCustomUserID is useful if you later want to use it for the segmentation:
    Game Overrides and Settings | Remote Config | 3.3.2
    in part for JEXL condition, if you set the customerUserID, you can set up a JEXL condition for the game override, something like user.customUserId == “xyz” , which will segment that particular user.
    Of course, you can add any attribute in user category, and later segment on it, but for customerUserID, it is done automatically.
    Therefore, customerUserID and analyticsUserId might have different purpose, but you are completely right in terms that customerUserID can be used if independently from UGS services. The customerUserID is solely feature of RC.

  2. You are absolutely correct. Nothing to add there, you got it all.

  3. This is also a part I am not sure of. I assume the latter, but for this, I might consult someone with more knowledge, and point them to this question.

Thanks!

2 Likes

Hi @Rabadash8820 ,

I’m going to try to just explain the ExternalUserId synergy with Analytics based on your questions below:

Also what sort of value does analyticsUserId have if I don’t manually override it with InitializationOptions.SetAnalyticsUserId()? Is it derived from SystemInfo.deviceUniqueIdentifier or something?

Either way, I still can’t tell if ExternalUserId is automatically updated by AuthenticationService.SignInAnonymouslyAsync or if I need to manually reset it in an AuthenticationService.SignedIn event handler. The fact that the Analytics package recommends setting it in the thread you linked would suggest that we do need to explicitly reset it, but idk.

ExternalUserId will not be automatically set by AuthenticationService.SignInAnonymouslyAsync, but Analytics will use the randomly generated userID from installationID if ExternalUserID is not set. The only reason you might use ExternalUserID for Analytics is if you’d like to set the userID to something customized, otherwise, you’d use the randomly generated userID that is assigned.

Furthermore, once a userID is assigned to a user/device, it will be cached and used on subsequent sessions. The userID can be overwritten at this point (using UnityServices.ExternalUserId = “USERNAME”;), but if not it will remain the same every session.

Note: ExternalUserId only became synergized with Analytics after Analytics package version 4.4.0.

2 Likes

Hi @Rabadash8820

I’ll add a few details to try to clarify a few points

  • Small clarification on SetEnvironmentId - it may still be useful for you. The environment id is only obtained when signing in to a player. If you fetch your remote config prior to signing in, it will by default always send you back your production values.
  • It’s important to know that the authentication player id and analytics user id are completely different identifiers.
  • The ExternalUserId is a way for customers to provide their own user id which will be used by Analytics only to determine the user id. It has no impact on Authentication in any way.
  • Authentication Profiles are only ways to locally scope/isolate player cache. We persist the player id and session token (which lets you relogin to the same account easily) by using this profile. Switching profiles can let you use different players without losing your cached credentials. It’s unrelated to all other identifiers.
4 Likes

Thanks for the input all, lots of great info to chew on here.

So is it correct to say that the “analytics user Id” (for which I don’t see any actual getter API) is the same field as UnityServices.ExternalUserId?

@vd_unity Is segmentation done automatically for userId also, or only for customUserId? It would seem kinda weird to only automatically handle the latter, since not all developers will use it, while the former is required.

@erickb_unity Regarding SetEnvironmentId and RC initialization, that’s interesting that you can fetch configs before signing in the player. The docs for the newer UPM version made it look like signing in first was required. Would RC still return prod settings even if you call InitializationOptions.SetEnvironmentName? Either way, it sounds like if you want to make the most specific, “fully qualified” RC request possible then you still want the player to be signed in first so that the values from their auth token are included. And in that case, I’m leaning more toward using SetEnvironmentName since it seems to tell all UGS services which environment we’re in, while RemoteConfigService.SetEnvironmentName is specific to RC. Plus names are more human-readable.

Also, if ExternalUserId is only used by Analytics, then why is it declared on UnitySetvices?

@RandolfKlemola So would you not recommend setting UnityServices.ExternalUserId to the active player ID after signing in? @erickb_unity mentioned how the Authentication player ID is a separate identifier from the analytics user Id, but shouldn’t we still ensure they have the same value?

@erickb_unity Are you saying that, if not for profiles, cached credentials would be lost every time a player signed out and then back in with a different ID? But then, how would you know which profiles to use in AuthenticationService.Instance.SwitchProfile while the player is signed out? If my game only uses anonymous auth, am I basically good to always leave the profile as default?

Hi @Rabadash8820 ,
Sorry for a bit slow reply!
Segmentation can be done on any attribute provided for 3 categories; user, app or unity
( more details here )

so if the developer wants to segment on the particular user, that can be done in two ways:

  1. using customUserId, where you provide your own userId and you can set it via SetCustomUserId("myCustomId"); which would automatically add it to user attributes to allow for segmentation JEXL like user.customUserId != "myCustomId"

  2. this can be achieved manually as well something like

public struct userAttributes { public string myUserId;}
userAttributes uAttributes = new userAttributes();
uAttributes.myUserId = "myCustomId";
.....
RemoteConfigService.Instance.FetchConfigs(uAttributes, new appAttributes());

and that could be later used in a JEXL like user.myUserId != "myCustomId"


There is no segmentation on userId and currently there is no getter for userId available to developer, so it can not be added to the attributes in the current state.

Hope this helps