Embed an AI voice agent with NowVoice

  • Release version: Zurich
  • Updated July 14, 2026
  • 4 minutes to read
  • 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 to apply custom colors. NowVoiceDefaultTheme is provided as a default.

    NowChat integration

    If your app uses NowChat, you can enable NowVoice in the chat interface. Set NowChatConfiguration 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:
      <key>NSMicrophoneUsageDescription</key>
      <string>This app uses your microphone to communicate with the ServiceNow voice agent.</string>
      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:).
        import NowSDK
        
        try NowSDK.configure(with: NowSDKConfiguration(
            authorizationProvider: self,
            permissionDelegate: self,
            logLevel: .error
        ))
      2. Implement NowSDKAuthorizationProviding to supply OAuth tokens for your instance.
        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.
        func canRequestPermission(_ permission: DevicePermission) -> Bool {
            return true
        }
    3. Create a NowVoiceService.
      Import NowVoice and call makeVoiceService(instanceUrl:) with the URL of your ServiceNow instance. This is an async function; call it from an async context or a Task.
      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 to retrieve the available voice endpoints. In most apps, there is a single endpoint.
      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:) to obtain a UIViewController with the voice agent UI. Present it full-screen.
      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.text)")
              },
              onCallEnded: { conversationId, error in
                  // Called when the voice session ends.
                  if let error {
                      print("Session ended with error: \(error)")
                  } else {
                      print("Session complete. Conversation ID: \(conversationId ?? "unknown")")
                  }
              }
          ),
          theme: NowVoiceDefaultTheme()
      )
      
      vc.modalPresentationStyle = .fullScreen
      present(vc, animated: true)
      Note:
      startVoice() fetches an OAuth access token automatically before launching the voice UI.
    6. Optional: Apply a custom theme.
      To match the voice UI to your app's visual identity, create a type conforming to NowVoiceThemeable and pass it to startVoice(endpoint:uiConfiguration:callbacks:theme:) or updateTheme(theme:).
      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. Optional: 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. 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:) to launch the voice UI. NowChat manages the voice session lifecycle internally.
      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 in
                  // Called when the embedded voice session ends
              }
          )
      )
      
      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

    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 in
                        self?.dismiss(animated: true)
                    }
                )
            )
            vc.modalPresentationStyle = .fullScreen
            present(vc, animated: true)
    
        } catch let error as NowServiceError {
            handleServiceError(error)
        }
    }