--- title: Embed an AI voice agent with NowVoice description: Use the NowVoice module to embed a real-time AI voice agent session into your iOS application.Configure NowVoice to add a real-time voice agent to your iOS application. locale: en-US canonical_url: https://www.servicenow.com/docs/r/australia/api-reference/developer-guides/mobsdk-ios-nowvoice.html release: australia product: Developer Guides classification: developer-guides topic_type: concept last_updated: "2026-07-14" reading_time_minutes: 5 breadcrumb: [Mobile SDK Developer Guide - iOS, Developer guides, API implementation and reference] --- # Embed an AI voice agent with NowVoice Use the NowVoice module to embed a real-time AI voice agent session into your iOS application. ## NowVoice features NowVoice gives your app a complete, ready-to-present voice agent UI. When a user starts a voice agent session, the SDK: - Authenticates against your ServiceNow instance using the token your app provides. - Opens a WebSocket connection to the configured voice agent endpoint. - Renders a full-screen native SwiftUI view with microphone controls and a live transcript. - Delivers real-time transcript messages and mute-state changes to your app through callbacks. - Shows an optional post-call transcript summary screen on session end. ## Theming NowVoice integrates with the NowUI theming system. Pass any type conforming to [`NowVoiceThemeable`](https://raw.githubusercontent.com/ServiceNow/ServiceNowDocs/australia/markdown/australia/api-reference/cllent-mobile-api-reference/NVoiceThemeableiOSProtocol.md) to apply custom colors. [`NowVoiceDefaultTheme`](https://raw.githubusercontent.com/ServiceNow/ServiceNowDocs/australia/markdown/australia/api-reference/cllent-mobile-api-reference/NVoiceDefThemeiOSStruct.md) is provided as a default. ## NowChat integration If your app uses NowChat, you can enable NowVoice in the chat interface. Set [`NowChatConfiguration`](https://raw.githubusercontent.com/ServiceNow/ServiceNowDocs/australia/markdown/australia/api-reference/cllent-mobile-api-reference/NowChatOptionsiOS.md) properties to configure voice within the NowChat UI and add a voice button. Tapping it launches the voice agent UI as a full-screen overlay within the chat flow. ## Mobile SDK instance settings The NowVoice module reads the following keys from your ServiceNow instance's Mobile SDK settings response. ``` { "sdk": { "voice": { "enabled": true, "configurations": [ { "voiceServiceId": "String", "voiceWebhookURL": "String", "mobileChannelType": "String", "mobileChannelId": "String" } ] } } } ``` ## Configure NowVoice Configure NowVoice to add a real-time voice agent to your iOS application. ### Before you begin Role required: admin - Mobile SDK 2.22.0 or later must be added to your Xcode project via Swift Package Manager. - Your ServiceNow instance must have voice agent services enabled. Confirm that `sdk.voice.enabled` is `true` in the Mobile SDK settings. ### Procedure 1. Add microphone usage description. Add the `NSMicrophoneUsageDescription` key to your app's `Info.plist`: ```xml NSMicrophoneUsageDescription This app uses your microphone to communicate with the ServiceNow voice agent. ``` **Note:** Omitting this key can cause iOS to crash the app when the voice session begins. 2. Configure NowSDK. If you have not already done so, configure NowSDK once at app launch. NowVoice requires NowSDK to be configured before any voice operations. 1. Call [`NowSDK.configure(with:)`](https://raw.githubusercontent.com/ServiceNow/ServiceNowDocs/australia/markdown/australia/api-reference/cllent-mobile-api-reference/NowSDKAPIiOS.md). ```swift import NowSDK try NowSDK.configure(with: NowSDKConfiguration( authorizationProvider: self, permissionDelegate: self, logLevel: .error )) ``` 2. Implement `NowSDKAuthorizationProviding` to supply OAuth tokens for your instance. ```swift func requestAuthorization(for instanceUrl: URL, completion: @escaping ([AuthorizationToken]?) -> Void) { // Retrieve and return your OAuth tokens here. completion(yourTokens) } ``` 3. Implement `DevicePermissionDelegate` to allow microphone access. ```swift func canRequestPermission(_ permission: DevicePermission) -> Bool { return true } ``` 3. Create a NowVoiceService. Import NowVoice and call [`makeVoiceService(instanceUrl:)`](https://raw.githubusercontent.com/ServiceNow/ServiceNowDocs/australia/markdown/australia/api-reference/cllent-mobile-api-reference/NowVoiceiOSAPI.md) with the URL of your ServiceNow instance. This is an async function; call it from an async context or a Task. ```swift import NowVoice let instanceUrl = URL(string: "https://your-instance.service-now.com")! do { let voiceService = try await NowVoice.makeVoiceService(instanceUrl: instanceUrl) } catch { // Handle NowServiceError print("Failed to create voice service: \(error)") } ``` 4. Select a voice endpoint. Use [`NowVoiceService.configurations`](https://raw.githubusercontent.com/ServiceNow/ServiceNowDocs/australia/markdown/australia/api-reference/cllent-mobile-api-reference/NowVoiceServiceiOSAPI.md) to retrieve the available voice endpoints. In most apps, there is a single endpoint. ```swift guard let endpoint = voiceService.configurations.first else { // No voice endpoints configured on this instance. return } ``` 5. Launch the voice UI. Call [`startVoice(endpoint:uiConfiguration:callbacks:theme:)`](https://raw.githubusercontent.com/ServiceNow/ServiceNowDocs/australia/markdown/australia/api-reference/cllent-mobile-api-reference/NowVoiceServiceiOSAPI.md) to obtain a `UIViewController` with the voice agent UI. Present it full-screen. ```swift let vc = try await voiceService.startVoice( endpoint: endpoint, uiConfiguration: NowVoiceUIConfiguration( hidesPostCallTranscript: false, shouldBlockTranscriptSharing: false ), callbacks: NowVoiceCallbacks( onMuteStateChanged: { isMuted in // Update your UI to reflect the current mute state. print("Microphone muted: \(isMuted)") }, onMessageReceived: { message in // Receive real-time transcript messages during the session. print("[\(message.role)]: \(message.content)") }, onCallEnded: { conversationId, error, endedFromCallKitUI in // Called when the voice session ends. if let error { print("Session ended with error: \(error)") } else { print("Session complete. Conversation ID: \(conversationId ?? "unknown")") } }, onCallMinimized: { // Called when the voice UI is minimized. print("Voice chat is minimized") } ), theme: NowVoiceDefaultTheme() ) vc.modalPresentationStyle = .fullScreen present(vc, animated: true) ``` **Note:** `startVoice()` fetches an OAuth access token automatically before launching the voice UI. 6. Apply a custom theme. To match the voice UI to your app's visual identity, create a type conforming to [`NowVoiceThemeable`](https://raw.githubusercontent.com/ServiceNow/ServiceNowDocs/australia/markdown/australia/api-reference/cllent-mobile-api-reference/NVoiceThemeableiOSProtocol.md) and pass it to [`startVoice(endpoint:uiConfiguration:callbacks:theme:)`](https://raw.githubusercontent.com/ServiceNow/ServiceNowDocs/australia/markdown/australia/api-reference/cllent-mobile-api-reference/NowVoiceServiceiOSAPI.md) or [`updateTheme(theme:)`](https://raw.githubusercontent.com/ServiceNow/ServiceNowDocs/australia/markdown/australia/api-reference/cllent-mobile-api-reference/NowVoiceServiceiOSAPI.md). ```swift struct MyVoiceTheme: NowVoiceThemeable { var color: NowUIColoring = MyAppColors() } // Apply at voice session launch let vc = try await voiceService.startVoice(endpoint: endpoint, theme: MyVoiceTheme()) // Or update while a voice session is active voiceService.updateTheme(theme: MyVoiceTheme()) ``` 7. Embed voice inside NowChat. If your app already uses NowChat, you can enable NowVoice in the chat interface. Pass the voice configuration directly to [`NowChatConfiguration`](https://raw.githubusercontent.com/ServiceNow/ServiceNowDocs/australia/markdown/australia/api-reference/cllent-mobile-api-reference/NowChatOptionsiOS.md). A voice button appears in the chat UI automatically when a valid voice endpoint is provided. **Note:** When using NowChat with voice, don't call [`startVoice(endpoint:uiConfiguration:callbacks:theme:)`](https://raw.githubusercontent.com/ServiceNow/ServiceNowDocs/australia/markdown/australia/api-reference/cllent-mobile-api-reference/NowVoiceServiceiOSAPI.md) to launch the voice UI. NowChat manages the voice session lifecycle internally. ```swift import NowChat import NowVoice // voiceConfiguration is optional — omit it to have the SDK use instance settings automatically let chatConfig = NowChatConfiguration( closePrompt: NowChatConfiguration.ClosePrompt( header: "Leave chat?", message: "Your conversation will end.", acceptButtonTitle: "Leave", declineButtonTitle: "Stay" ), disabledFeatures: [], conversationOptions: [.forceNewConversation], uiConfiguration: NowChatConfiguration.UIConfiguration( closeButton: .text("Close"), attachmentUploadButton: .init(isVisible: true), hideBranding: false ), voiceConfiguration: voiceService.configurations.first, // optional endpoint override voiceUIConfiguration: NowVoiceUIConfiguration( hidesPostCallTranscript: false, shouldBlockTranscriptSharing: false ), voiceCallbacks: NowVoiceCallbacks( onMuteStateChanged: { isMuted in print("Muted: \(isMuted)") }, onMessageReceived: { message in print("Transcript: \(message)") }, onCallEnded: { conversationId, error, endedFromCallKitUI in // Called when the embedded voice session ends }, onCallMinimized: { // Called when the voice UI is minimized. print("Voice chat is minimized") } ) ) let chatVC = try await chatService.startChat(configuration: chatConfig) present(chatVC, animated: true) ``` ### Result The voice agent UI is presented full-screen. The user can begin a real-time voice session with transcript and mute-state updates delivered to your app through the configured callbacks. ### Complete example ```swift import NowSDK import NowVoice // --- App launch --- try NowSDK.configure(with: NowSDKConfiguration( authorizationProvider: self, permissionDelegate: self )) // --- When the user taps "Start Voice" --- func startVoiceSession() async { let instanceUrl = URL(string: "https://your-instance.service-now.com")! do { let voiceService = try await NowVoice.makeVoiceService(instanceUrl: instanceUrl) guard let endpoint = voiceService.configurations.first else { return } let vc = try await voiceService.startVoice( endpoint: endpoint, callbacks: NowVoiceCallbacks( onMuteStateChanged: { [weak self] isMuted in self?.updateMuteIndicator(isMuted: isMuted) }, onMessageReceived: { [weak self] message in self?.appendTranscript(message) }, onCallEnded: { [weak self] _, error, endedFromCallKitUI in self?.dismiss(animated: true) }, onCallMinimized: { // Called when the voice UI is minimized. print("Voice chat is minimized") } ) ) vc.modalPresentationStyle = .fullScreen present(vc, animated: true) } catch let error as NowServiceError { handleServiceError(error) } } ```