iOS quickstart

Last updated  Sep 9, 2026

Install the package, implement one delegate, present the assistant. Your app ships no assistant UI of its own.

The SDK is a UIKit host with a SwiftUI interior, distributed as a binary XCFramework over Swift Package Manager, targeting iOS 16 and above. Android, Flutter, and React Native follow the same four steps.

Install the SDK

Add the package in Xcode and pick the VoqalSDK library. There is nothing to vendor and no build phase to configure.

Package.swift
// File → Add Package Dependencies… → paste the URL, or add it to Package.swift:.package(url: "https://github.com/VoqalAI/voqal-ios", from: "2.1.0")

Then import VoqalSDK wherever you configure the app.

Configure at launch

Call setup once, as early as you can. Calling prewarm straight after opens the backend connection in the background so the first turn skips a cold handshake — it is a no-op until your delegate can supply a token.

AppDelegate.swift
import VoqalSDKfunc application(  _ application: UIApplication,  didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {  var configuration = VoqalSDKConfiguration(    requestId: "prod-\(session.userId)",    theme: VoqalTheme(accent: "#2D5BFF", accent2: "#5EC5F1", appearance: .auto)  )  configuration.apiKey = "pk_live_…"  // sent as X-Voqal-Key  VoqalSDKManager.shared.setup(configuration: configuration)  VoqalSDKManager.shared.prewarm(delegate: coordinator)  return true}
The publishable key is not a secret.It identifies your tenant so the engine knows which prompt, tools, and theme to load. Your user's own backend token is what authorises data access, and that arrives from the delegate on every request.

Configuration options

OptionTypeRequiredDescription
requestIdStringRequiredRoutes the turn by prefix: prod- reaches your production backend, stg- reaches staging.
apiKeyString?RequiredYour publishable key, sent as X-Voqal-Key. Resolves which tenant config to load.
agentURLURL?OptionalOverrides the baked engine URL. Point it at localhost while developing.
themeVoqalThemeOptionalAccent pair, appearance (.light, .dark, .auto), and corner radius.
presentationStyleVoqalPresentationStyleOptional.sheet (default) or .fullScreen.
recordingInteractionModeRecordingInteractionModeOptionalTap-to-toggle or hold-to-talk on the mic control.

Implement the delegate

VocalButtonDelegate is the whole integration surface: five required methods, all synchronous. Two optional hooks — voqalForwardedHeaders() and voqalDidTapActionButton() — are covered below.

AppCoordinator+Voqal.swift
extension AppCoordinator: VocalButtonDelegate {  // Read live on every request — always hand back a current token.  func getToken() -> String { session.accessToken }  func getMetaData() -> String? {    #"{"country_code":"EGY","user_id":"84213"}"#  }  func getViewController() -> UIViewController { navigationController }  func voqalButton(didUploadRecording result: String) {    analytics.track("voqal_turn", result)  }  func voqalButton(didFailWith error: Error) {    logger.error("voqal", error)  }}
MethodReturnsCalled when
getToken()StringEvery request. Return your user's current backend token — the SDK never caches it.
getMetaData()String?Every request. A JSON string of client context, sent as X-Client-Metadata.
getViewController()UIViewControllerThe assistant needs a presenter, for example to push one of your own screens.
voqalButton(didUploadRecording:)VoidA voice turn finished. The payload is the transcript result.
voqalButton(didFailWith:)VoidA turn failed. Log it — the assistant has already shown the user a recoverable state.
Return a fresh token from getToken() every time. The SDK reads it live on each request rather than holding a copy, so handing back a token you captured at login will start failing the moment it expires.

Present the assistant

Open it from your own button, tab, or deep link. It comes up as a near-full sheet by default and manages its own navigation from there.

HomeViewController.swift
@objc func openAssistant() {  VoqalSDKManager.shared.presentChat(from: self, delegate: coordinator)}// Optional: forward every SDK event into your own logging.VoqalSDKManager.shared.addLogSink(VoqalConsoleLogSink())

Add NSMicrophoneUsageDescription to your Info.plist before you run — iOS terminates the app on first microphone access without it.

Your own button or the Voqal button

Two ways to give users a way in. VoqalButton is a ready-made circular launcher (110pt, accent-colored, your voqalButtonIcon inside) that presents the assistant when tapped — drop it in and pin it. Or use no Voqal chrome at all and call presentChat(from:delegate:) from your own bar button, tab, cell, or deep link. Both reach the same assistant.

HomeViewController.swift
// The provided launcher: a 110pt circular button that presents on tap.let button = VoqalButton()button.delegate = coordinator          // your VocalButtonDelegatebutton.presentingViewController = selfview.addSubview(button)// Pin it bottom-trailing with Auto Layout; its intrinsic size is 110×110.// Or skip VoqalButton entirely and present from anything of yours:@IBAction func askTapped(_ sender: UIBarButtonItem) {  VoqalSDKManager.shared.presentChat(from: self, delegate: coordinator)}

The assistant comes up as a near-full sheet by default, swipe-to-dismiss. For an edge-to-edge screen that closes only from its own button, set presentationStyle on the configuration — it is a setup-time setting, not a per-call one.

AppDelegate.swift
// Setup-time setting: apply before presentChat.configuration.presentationStyle = .fullScreen   // default: .sheetVoqalSDKManager.shared.setup(configuration: configuration)

Forward headers to your backend

New in 2.0.0.Some backends need more than the user's token — an API version, a tenant id, a feature flag. Implement the optional voqalForwardedHeaders() to hand Voqal a dictionary; the SDK sends each entry to the engine as X-Voqal-Forward-<name>, and the engine strips the prefix and forwards the value verbatim to your backend or MCP. Nothing to configure in the dashboard.

AppCoordinator+Voqal.swift
// Delegate method — read live each turn, like getToken().func voqalForwardedHeaders() -> [String: String] {  [    "Api-Version": "2024-10",    "Tenant-Id": session.tenantId,    "Authorization": "Bearer \(session.partnerToken)",  ]}

The dictionary above travels as X-Voqal-Forward-Api-Version, X-Voqal-Forward-Tenant-Id, and X-Voqal-Forward-Authorization. Like getToken(), it is read live on every request, so a rotated value applies on the next turn.

  • Authorizationis forwardable — send a bearer token for your own backend alongside the user's X-Token.
  • At most 32 headers are forwarded; extras are dropped rather than failing the turn.
  • An empty dictionary — the default — forwards nothing, so existing integrations are unaffected.
Reserved control headers are dropped. A key that collides, case-insensitively, with a Voqal control header — x-token, x-voqal-key, x-request-id, x-client-metadata, content-type, accept — is ignored, so it can never displace auth or routing.

Metadata vs forwarded headers

Two delegate hooks carry host data, to two different consumers. Use getMetaData() for context the assistant should reason about; use voqalForwardedHeaders() for values your backend needs on the wire.

Delegate hookWire headerRead by
getMetaData()X-Client-MetadataThe Voqal engine — client context (country, user id) that shapes routing and prompts.
voqalForwardedHeaders()X-Voqal-Forward-<name>Your backend or MCP — the engine strips the prefix and passes each value through verbatim.

Redirect & checkout

A turn often ends in an action — open checkout, view an order, follow a link. Voqal offers three ways to get the user there, from a hard handoff you own to fully automatic link cards.

Action button

Set configuration.actionButton to show an accent button beside the voice dock. Tapping it fires voqalDidTapActionButton() with no payload — you own the route. Use it for a fixed destination such as a cart or a checkout screen pushed from getViewController().

It is off by default: leave actionButton nil and nothing is drawn beside the voice wave. Set it only when your product has a destination worth a permanent button; a booking or support assistant usually needs none.

AppCoordinator+Voqal.swift
// Show the button, to the right of the voice dock…configuration.actionButton = VoqalActionButton(accessibilityLabel: "Checkout")// …then handle the tap. No payload — you own the route.func voqalDidTapActionButton() {  navigationController.pushViewController(CheckoutViewController(), animated: true)}

Confirm widget

When the agent runs a money-moving or otherwise sensitive action, the engine emits a confirm widget automatically — an in-conversation checkout the user approves inline. High-risk actions gate behind Face ID; you write no code for this.

Any URL in the agent's answer becomes a tappable card that opens through the operating system. No configuration — return a link from your backend and the SDK renders it.

Next steps

You now have a working assistant. From here, most teams pick up one of these:

  • Connect your backend's MCP server so the agent answers from real data instead of general knowledge.
  • Read the widget catalogue to see what the agent can draw, and which kinds require a confirm step.
  • Attach a log sink and forward SDK diagnostics into your existing error reporting.
  • Work through the engine API if you need to drive a turn from somewhere other than an app.
© 2026 VoqalVoqal SDK & engine documentation