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 guide

This 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
)
ParameterRequiredDescription
streamIdYesYour event stream ID from Data hub.
baseUrlNoAPI base URL (defaults to https://api.exponea.com).
🚧

Warning

Do not pass authorization, projectToken, or projectMapping when 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

setSdkAuthToken only 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
    }
}
🚧

Important

Register the error handler before calling setSdkAuthToken so 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 an Authorization header; 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 statusBehavior
401: UnauthorizedError 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 tokenError handler invoked (.notProvided). Same retry-once flow as 401.
403: with a valid tokenThe 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() or anonymize(completion:) is called.

  • stopIntegration() or stopIntegration(completion:) is called.

  • clearLocalCustomerData(appGroup:) is called.

  • identifyCustomer(context:properties:timestamp:) is called without a jwtToken in the CustomerIdentity.

Generate JWT tokens

Your backend must generate JWT tokens that match the event stream's validation requirements.

Header:

  • alg: Algorithm (HS256, HS384, or HS512).

  • 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.


© Bloomreach, Inc. All rights reserved.