Authentication Analytics
The Authentication SDK provides callbacks so you can track authentication state changes — configuration, login, token refresh, and logout. You can use these to update your UI, forward events to your analytics platform, or trigger downstream logic.
Both iOS and Android expose a state machine with nearly identical states. The key difference is the delivery mechanism: iOS uses a delegate protocol, while Android uses LiveData.
Overview
Aspect | iOS | Android |
|---|---|---|
State API |
|
|
Delivery | Delegate callback with |
|
State enum |
|
|
Backend context |
| Not provided per-state; query with |
Error context |
| Not provided per-state; use |
Federated login details | States only (e.g., | States + |
Service States
Both platforms share the same state flow. States are grouped into four phases.
Configuration States
These fire during SDK initialization (TMAuthentication.Builder.build()) before any user interaction. During configuration, multiple backend services (Host, Archtics, MicroFlex) may be configured, causing multiple serviceConfigured callbacks — one per backend. serviceConfigurationCompleted fires only once when all backends have finished configuring.
iOS ( | Android ( | Description |
|---|---|---|
|
| Configuration has started. |
|
| A specific backend has been configured. May fire multiple times. |
|
| All backends have finished configuring. The SDK is ready for login. |
Querying configured services:
You can check which services were configured at any time:
// Check which backends are configured via TMAuthentication.BackendService
// The backend parameter in onStateChanged tells you which service was configured
// Check which backends are configured via the configuration object
val config = tmAuthentication.configuration
val hasHost = config.modernAccounts?.host != null
val hasArchtics = config.modernAccounts?.archtics != null
val hasMfx = config.modernAccounts?.mfx != null
Login States
These fire during the login flow. During login, multiple services may be logged in (for example, Archtics and Host). The loggedIn state may fire multiple times — once per backend service. loginCompleted fires only once when the entire login process (including link-account flows) has finished.
iOS ( | Android ( | Description |
|---|---|---|
|
| Login has started. |
— |
| (Android only) Token exchange is in progress. |
|
| A login page has been presented for a backend. |
|
| The user has logged in to a specific backend. May fire multiple times. |
|
| The user manually cancelled the login. |
|
| Login failed for a specific backend. |
|
| A link-account prompt has been presented. |
|
| The entire login process has completed. |
Querying logged-in services:
// The backend parameter in onStateChanged tells you which service was logged in
// Check which backends have valid tokens
AuthSource.entries.forEach { source ->
val token = tmAuthentication.getToken(source)
if (token != null) {
Log.d("Auth", "$source is logged in")
}
}
Token Refresh States
iOS ( | Android ( | Description |
|---|---|---|
|
| The user's auth token has been refreshed. May fire multiple times during the app's lifetime. |
Logout States
When you call logout, by default the system logs out of all currently configured services. This means the loggedOut state may fire multiple times — once per backend. logoutCompleted fires only once at the end.
iOS ( | Android ( | Description |
|---|---|---|
|
| Logout has started. |
|
| The user has logged out of a specific backend. May fire multiple times. |
|
| The entire logout process has completed. |
Setup
iOS
Set yourself as the delegate on the shared TMAuthentication instance:
TMAuthentication.shared.delegate = self
Then conform to TMAuthenticationDelegate:
extension MyViewController: TMAuthenticationDelegate {
func onStateChanged(
backend: TMAuthentication.BackendService?,
state: TMAuthentication.ServiceState,
error: (Error)?) {
// Handle state change
}
}
Parameters:
Name | Type | Description |
|---|---|---|
|
| The backend service (Host, Archtics, etc.) the state relates to. |
|
| The current authentication state. |
|
| An error, if one occurred during the state transition. |
Note: Always include an
@unknown defaultcase to handle new states that may be added in future SDK versions.
Android
Observe the login state LiveData from your TMAuthentication instance:
tmAuthentication.getLoginStateLiveData().observe(this) { state ->
Log.d("Auth", "State changed: $state")
}
Android also provides login outcomes via ActivityResult:
private val loginLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
when (result.resultCode) {
Activity.RESULT_OK -> handleLoginSuccess()
Activity.RESULT_CANCELED -> handleLoginCancelled()
}
}
Note: Use both mechanisms together —
getLoginStateLiveData()gives you granular state transitions, whileActivityResultgives you the final login outcome.
Federated Login Events (Android)
In addition to the ServiceLoginState state machine, the Android SDK emits button-level federated login analytics through UserAnalyticsDelegate. These provide more detail about user interactions during the link-account flow:
Action | Description |
|---|---|
| Link-accounts screen was presented |
| Link-accounts screen was dismissed |
| Login dismissed after success without linking |
| User pressed "Link Accounts" |
| User pressed "No Thanks" |
Note: These
actionNamestrings are prefixed withcom.ticketmaster.presencesdk.eventanalytic.action..
UserAnalyticsDelegate.handler.observe(this) { data ->
data?.let {
if (it.actionName.contains("FED_LOGIN")) {
Log.d("AuthAnalytics", "Federated login event: ${it.actionName}")
}
}
}
Session Expiration
iOS
Session expiration is handled via TMTicketsOrderDelegate. See Tickets SDK Analytics for details.
Android
Observe session expiration (401 errors) via the sessionExpiredDelegate Flow:
lifecycleScope.launch {
TicketsSDKSingleton.sessionExpiredDelegate.collect {
Log.d("Auth", "Session expired")
TicketsSDKSingleton.logout {
launchLogin()
}
}
}
Complete Examples
iOS
TMAuthentication.shared.delegate = self
extension MyViewController: TMAuthenticationDelegate {
func onStateChanged(
backend: TMAuthentication.BackendService?,
state: TMAuthentication.ServiceState,
error: (Error)?) {
if let backend = backend {
if let error = error {
print("Auth: .\(state.rawValue) backend: \(backend.description) error: \(error.localizedDescription)")
} else {
print("Auth: .\(state.rawValue) backend: \(backend.description)")
}
} else {
if let error = error {
print("Auth: .\(state.rawValue) error: \(error.localizedDescription)")
} else {
print("Auth: .\(state.rawValue)")
}
}
switch state {
// Configuration
case .serviceConfigurationStarted:
break
case .serviceConfigured:
break
case .serviceConfigurationCompleted:
// SDK is ready — you can now trigger login
break
// Login
case .loginStarted:
break
case .loginPresented:
break
case .loggedIn:
break
case .loginAborted:
break
case .loginFailed:
if let error = error {
print("Login failed: \(error.localizedDescription)")
}
case .loginLinkAccountPresented:
break
case .loginCompleted:
// Login fully complete — safe to update UI
break
// Token
case .tokenRefreshed:
break
// Logout
case .logoutStarted:
break
case .loggedOut:
break
case .logoutCompleted:
// Logout fully complete — update UI
break
@unknown default:
break
}
}
}
Android
class MainActivity : AppCompatActivity() {
private lateinit var tmAuthentication: TMAuthentication
private val loginLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
when (result.resultCode) {
Activity.RESULT_OK -> showEventsScreen()
Activity.RESULT_CANCELED -> Log.d("Auth", "Login cancelled or failed")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupSDK()
}
private fun setupSDK() {
lifecycleScope.launch {
TMAuthentication.Builder(
apiKey = BuildConfig.CONSUMER_KEY,
clientName = BuildConfig.TEAM_NAME
)
.region(TMXDeploymentRegion.US)
.environment(TMXDeploymentEnvironment.Production)
.build(this@MainActivity)
.fold(
onSuccess = { auth ->
tmAuthentication = auth
observeAuthState()
},
onFailure = { error ->
Log.e("Auth", "Configuration failed: ${error.message}")
}
)
}
}
private fun observeAuthState() {
// Observe granular state transitions
tmAuthentication.getLoginStateLiveData().observe(this) { state ->
Log.d("Auth", "State: $state")
when (state) {
ServiceLoginState.SERVICE_CONFIGURATION_COMPLETED -> {
// SDK is ready — launch login
launchLogin()
}
ServiceLoginState.LOGIN_COMPLETED -> {
// Login fully complete
showEventsScreen()
}
ServiceLoginState.LOGIN_FAILED -> {
Log.e("Auth", "Login failed")
}
ServiceLoginState.LOGIN_LINK_ACCOUNT_PRESENTED -> {
Log.d("Auth", "Link account screen presented")
}
ServiceLoginState.LOGOUT_COMPLETED -> {
// Logout complete — return to login screen
launchLogin()
}
else -> { }
}
}
}
private fun launchLogin() {
loginLauncher.launch(tmAuthentication.getLoginIntent(this))
}
private fun showEventsScreen() {
TicketsSDKSingleton.getEventsFragment(this)?.let { fragment ->
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, fragment)
.commit()
}
}
}
Common Patterns
Waiting for Login to Complete
On both platforms, wait for loginCompleted / LOGIN_COMPLETED before updating your UI. The loggedIn / LOGGED_IN state fires per-backend and login may still be in progress.
Handling Multiple Backends
During login with multiple backends (e.g., Host + Archtics), you will see a sequence like:
LOGIN_STARTED
LOGIN_PRESENTED (for Archtics)
LOGGED_IN (for Archtics)
LOGIN_PRESENTED (for Host)
LOGGED_IN (for Host)
LOGIN_COMPLETED (once, at the end)
The same pattern applies to configuration and logout — intermediate states fire per-backend, while the *_COMPLETED state fires once at the end.
Related Documentation
Authentication — Authentication setup and configuration
Configuration — SDK configuration guide