Configure iOS SDK with JWT authentication
This guide walks you through configuring the Exponea iOS SDK to route tracking through Data hub event streams and to set up up JWT authentication.
Platform guideThis guide is for iOS developers. If you're using Android, see Configure Android SDK with JWT authentication.
Prerequisites
Before configuring Data hub integration:
-
Event stream created: You need at least one event stream in Data hub.
-
Event stream credentials: Your stream ID from Data hub (found in Events > your stream > Access security).
-
For JWT authentication: Backend infrastructure capable of generating and signing JWT tokens using your JWT signing keys.
Configure SDK for Data hub
Use the StreamSettings initialization to route tracking through event streams:
Basic configuration:
import ExponeaSDK
Exponea.shared.configure(
Exponea.StreamSettings(
streamId: "YOUR_STREAM_ID",
baseUrl: "https://api.exponea.com"
),
pushNotificationTracking: .disabled
)| Parameter | Required | Description |
|---|---|---|
streamId | Yes | Your event stream ID from Data hub. |
baseUrl | No | API base URL (defaults to https://api.exponea.com). |
WarningDo not pass
authorization,projectToken, orprojectMappingwhen using stream mode. These are Engagement-only parameters and are ignored.
With push notifications enabled:
Exponea.shared.configure(
Exponea.StreamSettings(
streamId: "YOUR_STREAM_ID",
baseUrl: "https://api.exponea.com"
),
pushNotificationTracking: .enabled(appGroup: "YOUR_APP_GROUP")
)For all configuration options and parameter availability between project and stream modes, see [iOS SDK configuration].
Set up JWT authentication
If your event stream uses JWT signed-only permissions for customer IDs, events, or properties, you must provide JWT tokens to authenticate tracking requests.
Set JWT token
Call setSdkAuthToken(_:) to provide the current JWT to the SDK:
Exponea.shared.setSdkAuthToken("YOUR_STREAM_JWT_TOKEN")This stores the token securely in the iOS Keychain and attaches it to all subsequent stream requests.
Note
setSdkAuthTokenonly has an effect when the SDK is configured with stream integration. The call is silently ignored in project/Engagement mode.
Register JWT error handler
Register a handler with setJwtErrorHandler(_:) to be notified whenever the JWT needs to be refreshed:
Exponea.shared.setJwtErrorHandler { context in
switch context.reason {
case .expiredSoon:
yourBackend.fetchNewJwt(for: context.customerIds) { newToken in
Exponea.shared.setSdkAuthToken(newToken)
}
case .expired, .notProvided, .invalid, .insufficient:
yourBackend.fetchNewJwt(for: context.customerIds) { newToken in
Exponea.shared.setSdkAuthToken(newToken)
}
default:
break
}
}
ImportantRegister the error handler before calling
setSdkAuthTokenso the proactive refresh timer can immediately notify the handler when the token approaches expiry.
For JwtErrorContext properties and reason values, see [iOS SDK configuration].
Identify customer with JWT
You can bundle customer IDs and the JWT together when identifying a customer:
let identity = CustomerIdentity(
customerIds: ["registered": "[email protected]"],
jwtToken: "YOUR_STREAM_JWT_TOKEN"
)
Exponea.shared.identifyCustomer(context: identity, properties: [:], timestamp: nil)If you omit jwtToken, the call clears any previously stored JWT. To keep the existing token when updating customer IDs, explicitly pass the current token.
JWT lifecycle management
The SDK actively manages the JWT lifecycle to ensure API requests succeed without interruption.
Proactive refresh timer
When setSdkAuthToken(_:) is called, the SDK parses the exp claim and schedules an internal timer that fires approximately 60 seconds before expiry. When the timer fires, the error handler is called with reason .expiredSoon, allowing your app to fetch and set a fresh token before any request fails.
Pre-flight token check
Before each stream HTTP request, the SDK inspects the current token state and calls the error handler if needed:
-
No token set: Handler called with
.notProvided. The request proceeds without anAuthorizationheader; the server returns 401/403, triggering the retry mechanism. -
Token already expired: Handler called with
.expired. The expired token is still sent; the server returns 401, triggering the retry mechanism. -
Token about to expire: Handler called with
.expiredSoon. The request proceeds normally with the current (still-valid) token.
Request retry on authentication failure
When a stream request fails with an authentication error, the SDK applies a single-retry strategy:
| HTTP status | Behavior |
|---|---|
| 401: Unauthorized | Error handler invoked (.expired or .invalid). SDK waits ~1 second, then retries once with the current token. If the retry fails, the request fails permanently. |
| 403: without a token | Error handler invoked (.notProvided). Same retry-once flow as 401. |
| 403: with a valid token | The token scope is insufficient for the requested stream. No retry, no token cleared, no error handler invoked. Request fails immediately. |
Token storage and clearing
The JWT is stored in the iOS Keychain (kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) so it persists across app launches.
The token is cleared automatically when:
-
anonymize()oranonymize(completion:)is called. -
stopIntegration()orstopIntegration(completion:)is called. -
clearLocalCustomerData(appGroup:)is called. -
identifyCustomer(context:properties:timestamp:)is called without ajwtTokenin theCustomerIdentity.
Generate JWT tokens
Your backend must generate JWT tokens that match the event stream's validation requirements.
Header:
-
alg: Algorithm (HS256,HS384, orHS512). -
kid: Your signing key ID from Data hub (optional but recommended).
Payload:
-
ids: Map of customer identifiers (example:{"registered": "user123"}). -
exp: Expiration timestamp (Unix epoch).
Key requirements:
-
The token must be signed with one of your JWT signing keys.
-
Customer IDs in the token must exactly match the IDs sent in tracking requests.
-
Expiration should match your session duration (maximum 90 days).
For complete JWT specification and backend code examples (Node.js, Python), see the backend implementation guide in [JWT authentication for Web SDK].
Complete integration example
The recommended setup order for a complete stream/Data hub integration:
import ExponeaSDK
// 1. Configure with StreamSettings
Exponea.shared.configure(
Exponea.StreamSettings(
streamId: "YOUR_STREAM_ID",
baseUrl: "https://api.exponea.com"
),
pushNotificationTracking: .enabled(appGroup: "YOUR_APP_GROUP")
)
// 2. Register JWT error handler BEFORE setting the token
Exponea.shared.setJwtErrorHandler { context in
switch context.reason {
case .expiredSoon:
yourBackend.fetchNewJwt(for: context.customerIds) { newToken in
Exponea.shared.setSdkAuthToken(newToken)
}
case .expired, .notProvided, .invalid:
yourBackend.fetchNewJwt(for: context.customerIds) { newToken in
Exponea.shared.setSdkAuthToken(newToken)
}
default:
break
}
}
// 3. Provide the initial JWT token
Exponea.shared.setSdkAuthToken("YOUR_INITIAL_JWT_TOKEN")
// 4. (Optional) Identify the customer with bundled JWT
let identity = CustomerIdentity(
customerIds: ["registered": "[email protected]"],
jwtToken: "YOUR_INITIAL_JWT_TOKEN"
)
Exponea.shared.identifyCustomer(context: identity, properties: [:], timestamp: nil)Validate and Troubleshoot implementation
For validation instructions, common issues, and solutions, see Validate and troubleshoot mobile SDK integration.
Next steps
After implementing Data hub integration:
-
[Configure event stream security and permissions] to define which customer IDs, events, and properties require JWT.
-
Review [iOS SDK tracking] for complete tracking implementation.
-
[Set up push notifications] to engage customers.
-
Implement [in-app personalization] for dynamic content.
-
[Configure App Inbox] for persistent messaging.
Updated about 4 hours ago