diff --git a/MIGRATION_GUIDE_v3_to_v5.md b/MIGRATION_GUIDE_v3_to_v5.md
index 7ae865c8c..14f190ba4 100644
--- a/MIGRATION_GUIDE_v3_to_v5.md
+++ b/MIGRATION_GUIDE_v3_to_v5.md
@@ -99,6 +99,14 @@ Once (or if) the user is no longer identifiable in your app (i.e. they logged ou
 
 Logging out has the affect of reverting to a “device-scoped” user, which is the new owner of the device’s push subscription.
 
+To observe changes to the onesignalId or externalId you can add a custom method to the event:
+    
+    OneSignal.User.Changed += _userStateChanged;
+
+    private void _userStateChanged(object sender, UserStateChangedEventArgs e) {
+        ...
+    }
+
 
 ## Subscriptions
 
@@ -165,7 +173,12 @@ The user name space is accessible via `OneSignal.User` and provides access to us
 | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | `IPushSubscription PushSubscription`                  | *The push subscription associated to the current user.*                                                                                                                                                                                  |
 | `string Language`                                     | *Set the 2-character language either as a detected language or explicitly set for this user.*                                                                                                                                            |
+| `string OneSignalId`                                  | *The UUID generated by OneSignal to represent a user, empty if this is currently unavailable.*                                                                                                                                            |
+| `string ExternalId`                                   | *The External ID is OneSignal's default and recommended alias label. This should be the mainidentifier you use to identify users. It is set when calling the [OneSignal.login] method.*                                                                                                                                            |
+| `Changed` <br><br> `event EventHandler<UserStateChangedEventArgs> Changed` <br><br> `UserStateChangedEventArgs { UserChangedState State }` | *Adds a change event that will run whenever the onesignalId or externalId has been changed.*                      |
+*                                                                                                                                            |
 | `PushSubsription.Changed` <br><br> `event EventHandler<PushSubscriptionChangedEventArgs> Changed` <br><br> `PushSubscriptionChangedEventArgs { PushSubscriptionChangedState State }` | *Adds a change event that will run whenever the push subscription has been changed.*                      |
+*                                                                                                                                            |
 | `void AddAlias(string label, string id)`              | *Set an alias for the current user.  If this alias already exists it will be overwritten.*                                                                                                                                               |
 | `void AddAliases(Dictionary<string, string> aliases)` | S*et aliases for the current user. If any alias already exists it will be overwritten.*                                                                                                                                                  |
 | `void RemoveAlias(string label)`                      | *Remove an alias from the current user.*                                                                                                                                                                                                 |
diff --git a/OneSignalExample/Assets/OneSignal/CHANGELOG.md b/OneSignalExample/Assets/OneSignal/CHANGELOG.md
index e85fb57cc..a702f1dc2 100644
--- a/OneSignalExample/Assets/OneSignal/CHANGELOG.md
+++ b/OneSignalExample/Assets/OneSignal/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ## [Unreleased]
 ### Fixed
 - iOS crash when calling OneSignal.User.PushSubscription.Id and OneSignal.User.PushSubscription.Token when they are null.
+### Changed
+- Add public getters for OneSignalId and ExternalId in the User namespace
+- Add public event handler OneSignal.User.Changed that fires when the OneSignalId or ExternalId changes
+- Updated included Android SDK to [5.1.4](https://github.com/OneSignal/OneSignal-Android-SDK/releases/tag/5.1.4)
 
 ## [5.0.6]
 ### Fixed
diff --git a/OneSignalExample/Assets/OneSignal/Example/OneSignalExampleBehaviour.cs b/OneSignalExample/Assets/OneSignal/Example/OneSignalExampleBehaviour.cs
index 8ad01c338..847dfb25d 100644
--- a/OneSignalExample/Assets/OneSignal/Example/OneSignalExampleBehaviour.cs
+++ b/OneSignalExample/Assets/OneSignal/Example/OneSignalExampleBehaviour.cs
@@ -33,6 +33,7 @@
 using OneSignalSDK;
 using OneSignalSDK.Notifications;
 using OneSignalSDK.InAppMessages;
+using OneSignalSDK.User;
 using OneSignalSDK.User.Models;
 using OneSignalSDK.Debug.Models;
 using OneSignalSDK.Debug.Utilities;
@@ -159,6 +160,7 @@ private void Start() {
 
         // Setup the below to listen for and respond to state changes
         OneSignal.User.PushSubscription.Changed += _pushSubscriptionChanged;
+        OneSignal.User.Changed += _userStateChanged;
     }
 
     /*
@@ -211,6 +213,11 @@ private void _pushSubscriptionChanged(object sender, PushSubscriptionChangedEven
         _log($"Push subscription changed to current: {JsonUtility.ToJson(e.State.Current)}");
     }
 
+    private void _userStateChanged(object sender, UserStateChangedEventArgs e) {
+        _log($"OneSignalId changed : {e.State.Current.OneSignalId}");
+        _log($"ExternalId changed : {e.State.Current.ExternalId}");
+    }
+
     /*
      * SDK setup
      */
@@ -298,6 +305,16 @@ public void RemoveAlias() {
         OneSignal.User.RemoveAlias(aliasKey);
     }
 
+    public void GetOneSignalId() {
+        string onesignalId = OneSignal.User.OneSignalId;
+        _log($"Get OneSignalId <b>{onesignalId}</b>");
+    }
+
+    public void GetExternalId() {
+        string externalId = OneSignal.User.ExternalId;
+        _log($"Get ExternalId <b>{externalId}</b>");
+    }
+
     /*
      * Push
      */
diff --git a/OneSignalExample/Assets/OneSignal/Example/OneSignalExampleScene.unity b/OneSignalExample/Assets/OneSignal/Example/OneSignalExampleScene.unity
index 8eed5e5ff..1edc6e35b 100644
--- a/OneSignalExample/Assets/OneSignal/Example/OneSignalExampleScene.unity
+++ b/OneSignalExample/Assets/OneSignal/Example/OneSignalExampleScene.unity
@@ -809,6 +809,85 @@ CanvasRenderer:
   m_PrefabAsset: {fileID: 0}
   m_GameObject: {fileID: 111296087}
   m_CullTransparentMesh: 1
+--- !u!1 &118443139
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 118443140}
+  - component: {fileID: 118443142}
+  - component: {fileID: 118443141}
+  m_Layer: 5
+  m_Name: Text
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!224 &118443140
+RectTransform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 118443139}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 0.9, y: 0.9, z: 1}
+  m_ConstrainProportionsScale: 0
+  m_Children: []
+  m_Father: {fileID: 1278232628}
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+  m_AnchorMin: {x: 0, y: 0}
+  m_AnchorMax: {x: 1, y: 1}
+  m_AnchoredPosition: {x: 0, y: 0}
+  m_SizeDelta: {x: 0, y: 0}
+  m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &118443141
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 118443139}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Material: {fileID: 0}
+  m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
+  m_RaycastTarget: 1
+  m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+  m_Maskable: 1
+  m_OnCullStateChanged:
+    m_PersistentCalls:
+      m_Calls: []
+  m_FontData:
+    m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
+    m_FontSize: 14
+    m_FontStyle: 0
+    m_BestFit: 1
+    m_MinSize: 10
+    m_MaxSize: 40
+    m_Alignment: 4
+    m_AlignByGeometry: 0
+    m_RichText: 1
+    m_HorizontalOverflow: 0
+    m_VerticalOverflow: 0
+    m_LineSpacing: 1
+  m_Text: Get OneSignalId
+--- !u!222 &118443142
+CanvasRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 118443139}
+  m_CullTransparentMesh: 1
 --- !u!1 &129243063
 GameObject:
   m_ObjectHideFlags: 0
@@ -3868,6 +3947,85 @@ CanvasRenderer:
   m_PrefabAsset: {fileID: 0}
   m_GameObject: {fileID: 484811935}
   m_CullTransparentMesh: 1
+--- !u!1 &495154762
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 495154763}
+  - component: {fileID: 495154765}
+  - component: {fileID: 495154764}
+  m_Layer: 5
+  m_Name: Text
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!224 &495154763
+RectTransform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 495154762}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 0.9, y: 0.9, z: 1}
+  m_ConstrainProportionsScale: 0
+  m_Children: []
+  m_Father: {fileID: 910262211}
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+  m_AnchorMin: {x: 0, y: 0}
+  m_AnchorMax: {x: 1, y: 1}
+  m_AnchoredPosition: {x: 0, y: 0}
+  m_SizeDelta: {x: 0, y: 0}
+  m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &495154764
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 495154762}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Material: {fileID: 0}
+  m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
+  m_RaycastTarget: 1
+  m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+  m_Maskable: 1
+  m_OnCullStateChanged:
+    m_PersistentCalls:
+      m_Calls: []
+  m_FontData:
+    m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
+    m_FontSize: 14
+    m_FontStyle: 0
+    m_BestFit: 1
+    m_MinSize: 10
+    m_MaxSize: 40
+    m_Alignment: 4
+    m_AlignByGeometry: 0
+    m_RichText: 1
+    m_HorizontalOverflow: 0
+    m_VerticalOverflow: 0
+    m_LineSpacing: 1
+  m_Text: Get ExternalId
+--- !u!222 &495154765
+CanvasRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 495154762}
+  m_CullTransparentMesh: 1
 --- !u!1 &499047144
 GameObject:
   m_ObjectHideFlags: 0
@@ -6747,6 +6905,139 @@ CanvasRenderer:
   m_PrefabAsset: {fileID: 0}
   m_GameObject: {fileID: 909918831}
   m_CullTransparentMesh: 1
+--- !u!1 &910262210
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 910262211}
+  - component: {fileID: 910262214}
+  - component: {fileID: 910262213}
+  - component: {fileID: 910262212}
+  m_Layer: 5
+  m_Name: button_getExternalId
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!224 &910262211
+RectTransform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 910262210}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_ConstrainProportionsScale: 0
+  m_Children:
+  - {fileID: 495154763}
+  m_Father: {fileID: 1250383578}
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+  m_AnchorMin: {x: 0, y: 0}
+  m_AnchorMax: {x: 0, y: 0}
+  m_AnchoredPosition: {x: 0, y: 0}
+  m_SizeDelta: {x: 0, y: 0}
+  m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &910262212
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 910262210}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Navigation:
+    m_Mode: 3
+    m_WrapAround: 0
+    m_SelectOnUp: {fileID: 0}
+    m_SelectOnDown: {fileID: 0}
+    m_SelectOnLeft: {fileID: 0}
+    m_SelectOnRight: {fileID: 0}
+  m_Transition: 1
+  m_Colors:
+    m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
+    m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+    m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
+    m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+    m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
+    m_ColorMultiplier: 1
+    m_FadeDuration: 0.1
+  m_SpriteState:
+    m_HighlightedSprite: {fileID: 0}
+    m_PressedSprite: {fileID: 0}
+    m_SelectedSprite: {fileID: 0}
+    m_DisabledSprite: {fileID: 0}
+  m_AnimationTriggers:
+    m_NormalTrigger: Normal
+    m_HighlightedTrigger: Highlighted
+    m_PressedTrigger: Pressed
+    m_SelectedTrigger: Selected
+    m_DisabledTrigger: Disabled
+  m_Interactable: 1
+  m_TargetGraphic: {fileID: 910262213}
+  m_OnClick:
+    m_PersistentCalls:
+      m_Calls:
+      - m_Target: {fileID: 307684281}
+        m_TargetAssemblyTypeName: OneSignalExampleBehaviour, OneSignal.UnityPackage.Example
+        m_MethodName: GetExternalId
+        m_Mode: 1
+        m_Arguments:
+          m_ObjectArgument: {fileID: 0}
+          m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
+          m_IntArgument: 0
+          m_FloatArgument: 0
+          m_StringArgument: 
+          m_BoolArgument: 0
+        m_CallState: 1
+--- !u!114 &910262213
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 910262210}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Material: {fileID: 0}
+  m_Color: {r: 1, g: 1, b: 1, a: 1}
+  m_RaycastTarget: 1
+  m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+  m_Maskable: 1
+  m_OnCullStateChanged:
+    m_PersistentCalls:
+      m_Calls: []
+  m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
+  m_Type: 1
+  m_PreserveAspect: 0
+  m_FillCenter: 1
+  m_FillMethod: 4
+  m_FillAmount: 1
+  m_FillClockwise: 1
+  m_FillOrigin: 0
+  m_UseSpriteMesh: 0
+  m_PixelsPerUnitMultiplier: 1
+--- !u!222 &910262214
+CanvasRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 910262210}
+  m_CullTransparentMesh: 1
 --- !u!1 &916273501
 GameObject:
   m_ObjectHideFlags: 0
@@ -10067,6 +10358,70 @@ CanvasRenderer:
   m_PrefabAsset: {fileID: 0}
   m_GameObject: {fileID: 1248003718}
   m_CullTransparentMesh: 1
+--- !u!1 &1250383577
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1250383578}
+  - component: {fileID: 1250383579}
+  m_Layer: 5
+  m_Name: inputcontrols_container
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!224 &1250383578
+RectTransform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1250383577}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_ConstrainProportionsScale: 0
+  m_Children:
+  - {fileID: 1278232628}
+  - {fileID: 910262211}
+  m_Father: {fileID: 1996857125}
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+  m_AnchorMin: {x: 0, y: 0}
+  m_AnchorMax: {x: 0, y: 0}
+  m_AnchoredPosition: {x: 0, y: 0}
+  m_SizeDelta: {x: 0, y: 0}
+  m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1250383579
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1250383577}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Padding:
+    m_Left: 0
+    m_Right: 0
+    m_Top: 0
+    m_Bottom: 0
+  m_ChildAlignment: 0
+  m_Spacing: 10
+  m_ChildForceExpandWidth: 1
+  m_ChildForceExpandHeight: 1
+  m_ChildControlWidth: 1
+  m_ChildControlHeight: 1
+  m_ChildScaleWidth: 0
+  m_ChildScaleHeight: 0
+  m_ReverseArrangement: 0
 --- !u!1 &1250632528
 GameObject:
   m_ObjectHideFlags: 0
@@ -10304,6 +10659,139 @@ CanvasRenderer:
   m_PrefabAsset: {fileID: 0}
   m_GameObject: {fileID: 1263276221}
   m_CullTransparentMesh: 1
+--- !u!1 &1278232627
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1278232628}
+  - component: {fileID: 1278232631}
+  - component: {fileID: 1278232630}
+  - component: {fileID: 1278232629}
+  m_Layer: 5
+  m_Name: button_getOneSignalId
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!224 &1278232628
+RectTransform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1278232627}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_ConstrainProportionsScale: 0
+  m_Children:
+  - {fileID: 118443140}
+  m_Father: {fileID: 1250383578}
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+  m_AnchorMin: {x: 0, y: 0}
+  m_AnchorMax: {x: 0, y: 0}
+  m_AnchoredPosition: {x: 0, y: 0}
+  m_SizeDelta: {x: 0, y: 0}
+  m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1278232629
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1278232627}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Navigation:
+    m_Mode: 3
+    m_WrapAround: 0
+    m_SelectOnUp: {fileID: 0}
+    m_SelectOnDown: {fileID: 0}
+    m_SelectOnLeft: {fileID: 0}
+    m_SelectOnRight: {fileID: 0}
+  m_Transition: 1
+  m_Colors:
+    m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
+    m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+    m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
+    m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+    m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
+    m_ColorMultiplier: 1
+    m_FadeDuration: 0.1
+  m_SpriteState:
+    m_HighlightedSprite: {fileID: 0}
+    m_PressedSprite: {fileID: 0}
+    m_SelectedSprite: {fileID: 0}
+    m_DisabledSprite: {fileID: 0}
+  m_AnimationTriggers:
+    m_NormalTrigger: Normal
+    m_HighlightedTrigger: Highlighted
+    m_PressedTrigger: Pressed
+    m_SelectedTrigger: Selected
+    m_DisabledTrigger: Disabled
+  m_Interactable: 1
+  m_TargetGraphic: {fileID: 1278232630}
+  m_OnClick:
+    m_PersistentCalls:
+      m_Calls:
+      - m_Target: {fileID: 307684281}
+        m_TargetAssemblyTypeName: OneSignalExampleBehaviour, OneSignal.UnityPackage.Example
+        m_MethodName: GetOneSignalId
+        m_Mode: 1
+        m_Arguments:
+          m_ObjectArgument: {fileID: 0}
+          m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
+          m_IntArgument: 0
+          m_FloatArgument: 0
+          m_StringArgument: 
+          m_BoolArgument: 0
+        m_CallState: 1
+--- !u!114 &1278232630
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1278232627}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Material: {fileID: 0}
+  m_Color: {r: 1, g: 1, b: 1, a: 1}
+  m_RaycastTarget: 1
+  m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+  m_Maskable: 1
+  m_OnCullStateChanged:
+    m_PersistentCalls:
+      m_Calls: []
+  m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
+  m_Type: 1
+  m_PreserveAspect: 0
+  m_FillCenter: 1
+  m_FillMethod: 4
+  m_FillAmount: 1
+  m_FillClockwise: 1
+  m_FillOrigin: 0
+  m_UseSpriteMesh: 0
+  m_PixelsPerUnitMultiplier: 1
+--- !u!222 &1278232631
+CanvasRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1278232627}
+  m_CullTransparentMesh: 1
 --- !u!1 &1296016975
 GameObject:
   m_ObjectHideFlags: 0
@@ -13786,7 +14274,7 @@ MonoBehaviour:
   m_HandleRect: {fileID: 916273502}
   m_Direction: 2
   m_Value: 1
-  m_Size: 0.5315627
+  m_Size: 0.50223213
   m_NumberOfSteps: 0
   m_OnValueChanged:
     m_PersistentCalls:
@@ -14155,6 +14643,7 @@ RectTransform:
   - {fileID: 1144247376}
   - {fileID: 1296016976}
   - {fileID: 629334645}
+  - {fileID: 1996857125}
   - {fileID: 262919209}
   m_Father: {fileID: 2924184}
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
@@ -16250,6 +16739,109 @@ Transform:
   m_Children: []
   m_Father: {fileID: 0}
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1996857124
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1996857125}
+  - component: {fileID: 1996857128}
+  - component: {fileID: 1996857127}
+  - component: {fileID: 1996857126}
+  m_Layer: 5
+  m_Name: getIds_container
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!224 &1996857125
+RectTransform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1996857124}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_ConstrainProportionsScale: 0
+  m_Children:
+  - {fileID: 1250383578}
+  m_Father: {fileID: 1654894820}
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+  m_AnchorMin: {x: 0, y: 0}
+  m_AnchorMax: {x: 0, y: 0}
+  m_AnchoredPosition: {x: 0, y: 0}
+  m_SizeDelta: {x: 0, y: 0}
+  m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1996857126
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1996857124}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Material: {fileID: 0}
+  m_Color: {r: 0.9607844, g: 0.47450984, b: 0.47450984, a: 1}
+  m_RaycastTarget: 1
+  m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+  m_Maskable: 1
+  m_OnCullStateChanged:
+    m_PersistentCalls:
+      m_Calls: []
+  m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
+  m_Type: 1
+  m_PreserveAspect: 0
+  m_FillCenter: 1
+  m_FillMethod: 4
+  m_FillAmount: 1
+  m_FillClockwise: 1
+  m_FillOrigin: 0
+  m_UseSpriteMesh: 0
+  m_PixelsPerUnitMultiplier: 1
+--- !u!222 &1996857127
+CanvasRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1996857124}
+  m_CullTransparentMesh: 1
+--- !u!114 &1996857128
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1996857124}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Padding:
+    m_Left: 10
+    m_Right: 10
+    m_Top: 10
+    m_Bottom: 10
+  m_ChildAlignment: 0
+  m_Spacing: 10
+  m_ChildForceExpandWidth: 1
+  m_ChildForceExpandHeight: 1
+  m_ChildControlWidth: 1
+  m_ChildControlHeight: 1
+  m_ChildScaleWidth: 0
+  m_ChildScaleHeight: 0
+  m_ReverseArrangement: 0
 --- !u!1 &2009596016
 GameObject:
   m_ObjectHideFlags: 0
diff --git a/OneSignalExample/Assets/Plugins/Android/mainTemplate.gradle b/OneSignalExample/Assets/Plugins/Android/mainTemplate.gradle
index 09525b310..7255df042 100644
--- a/OneSignalExample/Assets/Plugins/Android/mainTemplate.gradle
+++ b/OneSignalExample/Assets/Plugins/Android/mainTemplate.gradle
@@ -6,7 +6,7 @@ apply plugin: 'com.android.library'
 dependencies {
     implementation fileTree(dir: 'libs', include: ['*.jar'])
 // Android Resolver Dependencies Start
-    implementation 'com.onesignal:OneSignal:5.1.2' // Packages/com.onesignal.unity.android/Editor/OneSignalAndroidDependencies.xml:6
+    implementation 'com.onesignal:OneSignal:5.1.4' // Packages/com.onesignal.unity.android/Editor/OneSignalAndroidDependencies.xml:6
 // Android Resolver Dependencies End
 **DEPS**}
 
diff --git a/OneSignalExample/ProjectSettings/AndroidResolverDependencies.xml b/OneSignalExample/ProjectSettings/AndroidResolverDependencies.xml
index 9eb42f5a1..f52f7b1b4 100644
--- a/OneSignalExample/ProjectSettings/AndroidResolverDependencies.xml
+++ b/OneSignalExample/ProjectSettings/AndroidResolverDependencies.xml
@@ -1,6 +1,6 @@
 <dependencies>
   <packages>
-    <package>com.onesignal:OneSignal:5.1.2</package>
+    <package>com.onesignal:OneSignal:5.1.4</package>
   </packages>
   <files />
   <settings>
diff --git a/com.onesignal.unity.android/Editor/OneSignalAndroidDependencies.xml b/com.onesignal.unity.android/Editor/OneSignalAndroidDependencies.xml
index 0bc1db54d..3ced2f24e 100644
--- a/com.onesignal.unity.android/Editor/OneSignalAndroidDependencies.xml
+++ b/com.onesignal.unity.android/Editor/OneSignalAndroidDependencies.xml
@@ -3,6 +3,6 @@
     <repositories>
       <repository>https://repo.maven.apache.org/maven2</repository>
     </repositories>
-    <androidPackage spec="com.onesignal:OneSignal:5.1.2" />
+    <androidPackage spec="com.onesignal:OneSignal:5.1.4" />
   </androidPackages>
 </dependencies>
\ No newline at end of file
diff --git a/com.onesignal.unity.android/Runtime/AndroidUserManager.cs b/com.onesignal.unity.android/Runtime/AndroidUserManager.cs
index 430a547ab..e72b6f7dd 100644
--- a/com.onesignal.unity.android/Runtime/AndroidUserManager.cs
+++ b/com.onesignal.unity.android/Runtime/AndroidUserManager.cs
@@ -27,8 +27,10 @@
 
 
 using UnityEngine;
+using System;
 using System.Collections.Generic;
 using OneSignalSDK.User;
+using OneSignalSDK.User.Internal;
 using OneSignalSDK.User.Models;
 using OneSignalSDK.Android.User.Models;
 using OneSignalSDK.Android.Utilities;
@@ -37,12 +39,21 @@ namespace OneSignalSDK.Android.User {
     internal sealed class AndroidUserManager : IUserManager {
         private readonly AndroidJavaObject _user;
         private AndroidPushSubscription _pushSubscription;
+        public event EventHandler<UserStateChangedEventArgs> Changed;
         
         public AndroidUserManager(AndroidJavaClass sdkClass) {
             _user = sdkClass.CallStatic<AndroidJavaObject>("getUser");
             _pushSubscription = new AndroidPushSubscription(_user);
         }
 
+        public string OneSignalId {
+            get => _user.Call<string>("getOnesignalId");
+        }
+
+        public string ExternalId {
+            get => _user.Call<string>("getExternalId");
+        }
+
         public IPushSubscription PushSubscription {
             get => _pushSubscription;
         }
@@ -93,7 +104,31 @@ public void RemoveSms(string sms)
             => _user.Call("removeSms", sms);
 
         public void Initialize() {
+            _user.Call("addObserver", new InternalUserChangedHandler(this));
             _pushSubscription.Initialize();
         }
+
+        private sealed class InternalUserChangedHandler : OneSignalAndroidJavaProxy {
+            private AndroidUserManager _parent;
+
+            public InternalUserChangedHandler(AndroidUserManager userManager) : base("user.state.IUserStateObserver") {
+                _parent = userManager;
+            }
+
+            /// <param name="state">UserChangedState</param>
+            public void onUserStateChange(AndroidJavaObject state) {
+                var currentJO = state.Call<AndroidJavaObject>("getCurrent");
+                var current = currentJO.ToSerializable<UserState>();
+
+                UserChangedState userChangedState = new UserChangedState(current);
+                UserStateChangedEventArgs args = new UserStateChangedEventArgs(userChangedState);
+
+                EventHandler<UserStateChangedEventArgs> handler = _parent.Changed;
+                if (handler != null)
+                {
+                    UnityMainThreadDispatch.Post(state => handler(_parent, args));
+                }
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/com.onesignal.unity.core/Editor/Platform/UserManager.cs b/com.onesignal.unity.core/Editor/Platform/UserManager.cs
index dfbe22775..e9393006d 100644
--- a/com.onesignal.unity.core/Editor/Platform/UserManager.cs
+++ b/com.onesignal.unity.core/Editor/Platform/UserManager.cs
@@ -25,11 +25,22 @@
  * THE SOFTWARE.
  */
 
+using System;
 using System.Collections.Generic;
 using OneSignalSDK.User.Models;
 
 namespace OneSignalSDK.User {
     internal sealed class UserManager : IUserManager {
+        public string OneSignalId {
+            get => "";
+        }
+
+        public string ExternalId {
+            get => "";
+        }
+
+        public event EventHandler<UserStateChangedEventArgs> Changed;
+
         private PushSubscription _subscription = new PushSubscription();
 
         public IPushSubscription PushSubscription {
diff --git a/com.onesignal.unity.core/Runtime/User/IUserManager.cs b/com.onesignal.unity.core/Runtime/User/IUserManager.cs
index 84c9418f3..f64dd606e 100644
--- a/com.onesignal.unity.core/Runtime/User/IUserManager.cs
+++ b/com.onesignal.unity.core/Runtime/User/IUserManager.cs
@@ -25,10 +25,25 @@
  * THE SOFTWARE.
  */
 
+using System;
 using System.Collections.Generic;
+using System.Threading.Tasks;
+using UnityEngine;
 using OneSignalSDK.User.Models;
 
 namespace OneSignalSDK.User {
+    /// <summary>
+    /// Several states associated with the SDK can be changed in and outside of the application.
+    /// </summary>
+    public class UserStateChangedEventArgs : EventArgs
+    {
+        public UserChangedState State { get; }
+
+        public UserStateChangedEventArgs(UserChangedState state) {
+            State = state;
+        }
+    }
+
     /// <summary>
     /// The OneSignal user manager is responsible for managing the current user state.  When
     /// an app starts up for the first time, it is defaulted to having a guest user that is only
@@ -46,6 +61,24 @@ namespace OneSignalSDK.User {
     ///   2. When the identity of the user is lost (i.e. a logout)
     /// </summary>
     public interface IUserManager {
+        /// <summary>
+        /// The UUID generated by OneSignal to represent a user, empty if this is currently unavailable.
+        /// </summary>
+        string OneSignalId { get; }
+
+        /// <summary>
+        /// The External ID is OneSignal's default and recommended alias label. This should be the main
+        /// identifier you use to identify users. It is set when calling the [OneSignal.login] method.
+        ///
+        /// This is empty if the External ID has not been set.
+        /// </summary>
+        string ExternalId { get; }
+
+        /// <summary>
+        /// When onesignalId or externalId has changed
+        /// </summary>
+        event EventHandler<UserStateChangedEventArgs> Changed;
+
         /// <summary>
         /// The 2-character language either as a detected language or explicitly set for this user. See
         /// https://documentation.onesignal.com/docs/language-localization#what-languages-are-supported
diff --git a/com.onesignal.unity.core/Runtime/User/Internal/UserState.cs b/com.onesignal.unity.core/Runtime/User/Internal/UserState.cs
new file mode 100644
index 000000000..eab372c57
--- /dev/null
+++ b/com.onesignal.unity.core/Runtime/User/Internal/UserState.cs
@@ -0,0 +1,46 @@
+/*
+ * Modified MIT License
+ *
+ * Copyright 2023 OneSignal
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * 1. The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 2. All copies of substantial portions of the Software may only be used in connection
+ * with services provided by OneSignal.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+using System;
+using OneSignalSDK.User;
+
+namespace OneSignalSDK.User.Internal {
+    [Serializable] public sealed class UserState {
+        public string OneSignalId => onesignalId;
+        public string ExternalId => externalId;
+
+        #region Native Field Handling
+            public string onesignalId;
+            public string externalId;
+        #endregion
+
+        public UserState(string onesignalId, string externalId) {
+            this.onesignalId = onesignalId;
+            this.externalId = externalId;
+        }
+    }
+}
\ No newline at end of file
diff --git a/com.onesignal.unity.core/Runtime/User/Internal/UserState.cs.meta b/com.onesignal.unity.core/Runtime/User/Internal/UserState.cs.meta
new file mode 100644
index 000000000..c58f687ca
--- /dev/null
+++ b/com.onesignal.unity.core/Runtime/User/Internal/UserState.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0dc78f38e7b0948d99aedd469f4a2a29
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/com.onesignal.unity.core/Runtime/User/Models/IPushSubscription.cs b/com.onesignal.unity.core/Runtime/User/Models/IPushSubscription.cs
index 378a948f6..fabdc919f 100644
--- a/com.onesignal.unity.core/Runtime/User/Models/IPushSubscription.cs
+++ b/com.onesignal.unity.core/Runtime/User/Models/IPushSubscription.cs
@@ -25,10 +25,10 @@
  * THE SOFTWARE.
  */
 
+using System;
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using UnityEngine;
-using System;
 
 namespace OneSignalSDK.User.Models {
     /// <summary>
diff --git a/com.onesignal.unity.core/Runtime/User/Models/IUserState.cs b/com.onesignal.unity.core/Runtime/User/Models/IUserState.cs
new file mode 100644
index 000000000..6bfcab177
--- /dev/null
+++ b/com.onesignal.unity.core/Runtime/User/Models/IUserState.cs
@@ -0,0 +1,46 @@
+/*
+ * Modified MIT License
+ *
+ * Copyright 2023 OneSignal
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * 1. The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 2. All copies of substantial portions of the Software may only be used in connection
+ * with services provided by OneSignal.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+using System;
+
+namespace OneSignalSDK.User {
+    public interface IUserState {
+        /// <summary>
+        /// The unique identifier for your OneSignal account. This will be an empty string until the
+        /// user has been successfully logged in on the backend and assigned an ID.
+        /// Use [Changed] to be notified when the [OneSignalId] has been successfully assigned.
+        /// </summary>
+        string OneSignalId { get; }
+        
+        /// <summary>
+        /// The external identifier that you use to identify users. Use [Changed] to be notified
+        /// when the [ExternalId] has been successfully assigned. This will be an empty string if no
+        /// external identifier has been assigned to the associated [OneSignalId].
+        /// </summary>
+        string ExternalId { get; }
+    }
+}
\ No newline at end of file
diff --git a/com.onesignal.unity.core/Runtime/User/Models/IUserState.cs.meta b/com.onesignal.unity.core/Runtime/User/Models/IUserState.cs.meta
new file mode 100644
index 000000000..93e556cbf
--- /dev/null
+++ b/com.onesignal.unity.core/Runtime/User/Models/IUserState.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 369a546d971624331b3abbb151bf363a
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/com.onesignal.unity.core/Runtime/User/Models/UserChangedState.cs b/com.onesignal.unity.core/Runtime/User/Models/UserChangedState.cs
new file mode 100644
index 000000000..419fe0336
--- /dev/null
+++ b/com.onesignal.unity.core/Runtime/User/Models/UserChangedState.cs
@@ -0,0 +1,39 @@
+/*
+ * Modified MIT License
+ *
+ * Copyright 2023 OneSignal
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * 1. The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 2. All copies of substantial portions of the Software may only be used in connection
+ * with services provided by OneSignal.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+using System;
+using OneSignalSDK.User.Internal;
+
+namespace OneSignalSDK.User {
+    public sealed class UserChangedState {
+        public UserState Current { get; }
+
+        public UserChangedState(UserState state) {
+            Current = state;
+        }
+    }
+}
\ No newline at end of file
diff --git a/com.onesignal.unity.core/Runtime/User/Models/UserChangedState.cs.meta b/com.onesignal.unity.core/Runtime/User/Models/UserChangedState.cs.meta
new file mode 100644
index 000000000..5b4c07c05
--- /dev/null
+++ b/com.onesignal.unity.core/Runtime/User/Models/UserChangedState.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 63e0183774569451eb3aeccb49a7ea1f
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/com.onesignal.unity.core/Samples~/OneSignalExampleBehaviour.cs b/com.onesignal.unity.core/Samples~/OneSignalExampleBehaviour.cs
index 1efb742a5..f2a0c64c5 100644
--- a/com.onesignal.unity.core/Samples~/OneSignalExampleBehaviour.cs
+++ b/com.onesignal.unity.core/Samples~/OneSignalExampleBehaviour.cs
@@ -159,6 +159,7 @@ private void Start() {
 
         // Setup the below to listen for and respond to state changes
         OneSignal.User.PushSubscription.Changed += _pushSubscriptionChanged;
+        OneSignal.User.Changed += _userStateChanged;
     }
 
     /*
@@ -211,6 +212,11 @@ private void _pushSubscriptionChanged(object sender, PushSubscriptionChangedEven
         _log($"Push subscription changed to current: {JsonUtility.ToJson(e.State.Current)}");
     }
 
+    private void _userStateChanged(object sender, UserStateChangedEventArgs e) {
+        _log($"OneSignalId changed : {JsonUtility.ToJson(e.State.Current.OneSignalId)}");
+        _log($"ExternalId changed : {JsonUtility.ToJson(e.State.Current.ExternalId)}");
+    }
+
     /*
      * SDK setup
      */
diff --git a/com.onesignal.unity.ios/Runtime/Plugins/iOS/OneSignalUnityBridgeUser.mm b/com.onesignal.unity.ios/Runtime/Plugins/iOS/OneSignalUnityBridgeUser.mm
index 0d4db99e9..5f005fd1f 100644
--- a/com.onesignal.unity.ios/Runtime/Plugins/iOS/OneSignalUnityBridgeUser.mm
+++ b/com.onesignal.unity.ios/Runtime/Plugins/iOS/OneSignalUnityBridgeUser.mm
@@ -31,6 +31,7 @@
 #import "OneSignalBridgeUtil.h"
 
 typedef void (*StateListenerDelegate)(const char* current, const char* previous);
+typedef void (*UserStateListenerDelegate)(const char* current);
 
 /*
  * Helpers
@@ -78,6 +79,45 @@ - (void)onPushSubscriptionDidChangeWithState:(OSPushSubscriptionChangedState*)st
 
 @end
 
+/*
+ * User state observer singleton for global callbacks
+ */
+
+@interface OneSignalUserStateObserver : NSObject <OSUserStateObserver>
+
++ (instancetype) sharedUserObserver;
+@property UserStateListenerDelegate userStateDelegate;
+
+@end
+
+@implementation OneSignalUserStateObserver
+
++ (instancetype) sharedUserObserver {
+    static dispatch_once_t pred = 0;
+    static id _sharedObject = nil;
+    dispatch_once(&pred, ^{
+        _sharedObject = [[self alloc] init];
+    });
+    return _sharedObject;
+}
+
+- (instancetype) init {
+    if (self = [super init]) {
+        [OneSignal.User addObserver:self];
+    }
+
+    return self;
+}
+
+- (void)onUserStateDidChangeWithState:(OSUserChangedState*)state {
+    if (_userStateDelegate != nil) {
+        auto curr = oneSignalJsonStringFromDictionary([[state current] jsonRepresentation]);
+        _userStateDelegate(curr);
+    }
+}
+
+@end
+
 /*
  * Bridge methods
  * We use strdup because Unity attempts to free the memory after we return the value
@@ -160,6 +200,20 @@ void _oneSignalUserRemoveTags(const char* tagsJson) {
         [OneSignal.User removeTags:tags];
     }
 
+    const char* _oneSignalUserGetOneSignalId(){
+        if (OneSignal.User.OneSignalId == NULL) {
+            return NULL;
+        }
+        return strdup([OneSignal.User.OneSignalId UTF8String]);
+    }
+
+    const char* _oneSignalUserGetExternalId(){
+        if (OneSignal.User.ExternalId == NULL) {
+            return NULL;
+        }
+        return strdup([OneSignal.User.ExternalId UTF8String]);
+    }
+
     const char* _oneSignalPushSubscriptionGetId() {
         if (OneSignal.User.pushSubscription.id == NULL) {
             return NULL;
@@ -189,4 +243,8 @@ void _oneSignalPushSubscriptionOptOut() {
     void _oneSignalPushSubscriptionAddStateChangedCallback(StateListenerDelegate callback) {
         [[OneSignalUserObserver sharedUserObserver] setPushSubscriptionDelegate:callback];
     }
+
+    void _oneSignalUserAddStateChangedCallback(UserStateListenerDelegate callback) {
+        [[OneSignalUserStateObserver sharedUserObserver] setUserStateDelegate:callback];
+    }
 }
diff --git a/com.onesignal.unity.ios/Runtime/iOSUserManager.cs b/com.onesignal.unity.ios/Runtime/iOSUserManager.cs
index 7b9822337..57ff77d43 100644
--- a/com.onesignal.unity.ios/Runtime/iOSUserManager.cs
+++ b/com.onesignal.unity.ios/Runtime/iOSUserManager.cs
@@ -27,14 +27,18 @@
 
 
 using UnityEngine;
+using System;
 using System.Collections.Generic;
 using System.Runtime.InteropServices;
 using OneSignalSDK.User;
+using OneSignalSDK.User.Internal;
 using OneSignalSDK.User.Models;
 using OneSignalSDK.iOS.User.Models;
 
 namespace OneSignalSDK.iOS.User {
     internal sealed class iOSUserManager : IUserManager {
+        [DllImport("__Internal")] private static extern string _oneSignalUserGetOneSignalId();
+        [DllImport("__Internal")] private static extern string _oneSignalUserGetExternalId();
         [DllImport("__Internal")] private static extern void _oneSignalUserSetLanguage(string languageCode);
         [DllImport("__Internal")] private static extern void _oneSignalUserAddAlias(string aliasLabel, string aliasId);
         [DllImport("__Internal")] private static extern void _oneSignalUserAddAliases(string aliasesJson);
@@ -49,13 +53,29 @@ internal sealed class iOSUserManager : IUserManager {
         [DllImport("__Internal")] private static extern void _oneSignalUserAddTags(string tagsJson);
         [DllImport("__Internal")] private static extern void _oneSignalUserRemoveTag(string key);
         [DllImport("__Internal")] private static extern void _oneSignalUserRemoveTags(string tagsJson);
+        [DllImport("__Internal")] private static extern void _oneSignalUserAddStateChangedCallback(UserStateListenerDelegate callback);
+
+        public delegate void UserStateListenerDelegate(string current);
+
+        public event EventHandler<UserStateChangedEventArgs> Changed;
 
         private iOSPushSubscription _pushSubscription;
+
+        private static iOSUserManager _instance;
         
         public iOSUserManager() {
+            _instance = this;
             _pushSubscription = new iOSPushSubscription();
         }
 
+        public string OneSignalId {
+            get => _oneSignalUserGetOneSignalId();
+        }
+
+        public string ExternalId {
+            get => _oneSignalUserGetExternalId();
+        }
+
         public IPushSubscription PushSubscription {
             get => _pushSubscription;
         }
@@ -109,6 +129,21 @@ public void RemoveSms(string sms)
 
         public void Initialize() {
             _pushSubscription.Initialize();
+            _oneSignalUserAddStateChangedCallback(_onUserStateChanged);
+        }
+
+        [AOT.MonoPInvokeCallback(typeof(UserStateListenerDelegate))]
+        private static void _onUserStateChanged(string current) {
+            var curr = JsonUtility.FromJson<UserState>(current);
+
+            UserChangedState userChangedState = new UserChangedState(curr);
+            UserStateChangedEventArgs args = new UserStateChangedEventArgs(userChangedState);
+
+            EventHandler<UserStateChangedEventArgs> handler = _instance.Changed;
+            if (handler != null)
+            {
+                UnityMainThreadDispatch.Post(state => handler(_instance, args));
+            }
         }
     }
 }
\ No newline at end of file