For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Help CenterOpenAPI SpecStatus
OverviewInquiriesTransactionsRelayAPI ReferenceChangelog
OverviewInquiriesTransactionsRelayAPI ReferenceChangelog
  • Overview
    • Inquiries Overview
    • Inquiry Model Lifecycle
    • Inquiry Templates
  • Managing inquiries
    • Creating Inquiries
    • Resuming Inquiries
    • Accessing Inquiry status and data
  • Hosted Flow integration
  • Embedded Flow integration
  • Mobile SDK integration
    • Overview
      • iOS Integration Guide
      • iOS Inline Inquiries Guide
      • iOS Changelog
      • iOS Privacy Manifest
      • Migrate iOS Theming from Client to Server
      • Migrate from iOS SDK v2 to v3
      • Migrate from iOS SDK v1 to v2
      • Tutorial: Pre-create inquiries for iOS
      • iOS Licenses
    • Webview
  • Troubleshooting
    • Troubleshooting Steps
    • Troubleshooting Common Issues
LogoLogo
Help CenterOpenAPI SpecStatus
On this page
  • Integration
  • Installation
  • Permissions
  • Privacy Configuration
  • Usage
  • Build and Launch an Inquiry
  • Persona recommends creating inquiries via API when possible
  • Handle Results
  • Do not rely on callbacks for critical business logic
  • Handle Errors
  • Configuration
  • Linking to a Persona Account
  • Linking by Reference ID
  • Pre-writing Fields
  • Resuming an Inquiry
  • Overriding Device Locale
  • Selecting an Environment
  • Redirect URI
  • Sharing Data with a Share Token
  • Customization
  • Theme Set
  • Style Variant
  • Custom Fonts
  • Initial Loading Screen
  • Government Id NFC Integration
  • Video Integration
  • Phone Number Silent Network Authentication (SNA) Integration
  • Licenses
Mobile SDK integrationiOS

iOS Integration Guide

Was this page helpful?
Previous

iOS Inline Inquiries Guide

A guide on how to launch Inquiries inline.
Next
Built with

The Persona iOS SDK lets you run identity verification flows in your iOS app from UIKit or SwiftUI. The SDK takes care of the hard parts of identity capture: selfies, government ID scanning and classification, NFC passport reads, document uploads, and silent network authentication. Each flow is configured from a template you manage in the Persona Dashboard.

Integration

The SDK requires a minimum deployment target of iOS 17.6 or later. If your app needs to support iOS versions below 17.6, please continue using the latest v2.x release.

Installation

Swift Package Manager
Manual Framework
CocoaPods (v2.x only)

To install the SDK with Swift Package Manager:

  1. Select your project’s Swift Packages tab.
  2. Click on the + to add a package.
  3. Add the repository URL https://github.com/persona-id/inquiry-ios-2.git, and click Next.
  4. Choose the package options version rule you want to use. We recommend the default (up to next major version), and click Next.
  5. Check that the package is being added to the right target and click Finish.

Permissions

In addition to importing the dependency, you also need to modify your Info.plist and add the required permissions:

  1. Navigate to your project’s settings in Xcode and click the Info tab.
  2. Add a new “Privacy - Camera Usage Description” (NSCameraUsageDescription) entry (if not already present) to enable camera access.
  3. Add a new “Privacy - Location When In Use Usage Description” (NSLocationWhenInUseUsageDescription) entry (if not already present) to enable GPS access. Unfortunately, Apple does not provide tools to differentiate when the API is in use. Therefore, even if your app or inquiry flow does not utilize the GPS functionality, the usage string must be included because the Persona SDK supports the functionality.
  4. Add a new “Privacy - Bluetooth Always Usage Description” (NSBluetoothAlwaysUsageDescription) entry (if not already present) to enable bluetooth scanning. Unfortunately, Apple does not provide tools to differentiate when the API is in use. Therefore, even if your app or inquiry flow does not utilize the bluetooth functionality, the usage string must be included because the Persona SDK supports the functionality.
  5. [Optional] If using our support for video verifications, add a new “Privacy - Microphone Usage Description” (NSMicrophoneUsageDescription) entry (if not already present) to enable microphone access.
  6. [Optional] If using our support for NFC verifications, “Privacy - NFC Scan Usage Description” (NFCReaderUsageDescription) entry (if not already present) to enable NFC access.

Privacy Configuration

This SDK collects a user’s IDFV for fraud prevention purposes. In App Store Connect > Your App > App Privacy, if you haven’t already add in a “Device Identifier,” and fill out the questionnaire with the following answers:

  • Usage: App Functionality (covers fraud prevention)
  • Are the device IDs collected from this app linked to the user’s identity? Yes
  • Do you or your third-party partners use device IDs for tracking purposes? No

Be sure to also update your privacy manifest according to the features you are making use of from the SDK. See our iOS Privacy Manifest instructions for more information.

Usage

Build and Launch an Inquiry

Persona recommends creating inquiries via API when possible

Please refer to Creating inquiries for more information. After getting up and running consider moving inquiry creation to your backend for security reasons.

Start an inquiry with a template ID, inquiry ID, or one-time link code. The samples below use a template ID — replace itmpl_EXAMPLE with your own. You can find your Inquiry Template ID in the Persona Dashboard.

When the flow is presented, the SDK takes control of the user interface. Once the flow completes, control returns to your app and the appropriate result handler is called.

UIKit
SwiftUI

Build the inquiry with Inquiry.from(templateId:delegate:) and call start(from:) with the presenting view controller.

1class MyViewController: UIViewController {
2
3 // This is hooked up to a button which starts the flow
4 @objc
5 private func buttonTapped(_ sender: UIButton) {
6 // Build the inquiry with the view controller as delegate
7 let inquiry = Inquiry.from(templateId: "itmpl_EXAMPLE", delegate: self)
8 .build()
9 .start(from: self) // start inquiry with view controller as presenter
10 }
11}

Handle Results

Do not rely on callbacks for critical business logic

SDK callbacks are intended for coordination between your app’s UI and Persona’s UI (e.g. opening and closing the flow UI). They do NOT guarantee that data are up-to-date, and cannot be reliably used to guarantee data integrity. Webhooks should be used for logic that depends on Inquiry state.

For more information, see Accessing Inquiry status and data.

UIKit
SwiftUI

To receive the inquiry result, implement the InquiryDelegate protocol. For example:

1extension MyViewController: InquiryDelegate {
2
3 func inquiryComplete(inquiryId: String, status: String, fields: [String : InquiryField]) {
4 // Inquiry completed
5 }
6
7 func inquiryCanceled(inquiryId: String?, sessionToken: String?) {
8 // Inquiry cancelled by user
9 }
10
11 func inquiryError(_ error: PersonaError) {
12 // Inquiry errored
13 }
14
15 // Optional: called periodically throughout the flow when events occur.
16 // Use for logging or analytics. The inquiry flow remains active during this callback.
17 func inquiryEventOccurred(event: InquiryEvent) {
18 switch event {
19 case .start(let start):
20 // Flow has completed initial network load
21 // start.inquiryId, start.sessionToken
22 case .pageChange(let pageChange):
23 // Current page changed
24 // pageChange.name (step name), pageChange.path (page within step)
25 }
26 }
27}

Handle Errors

In rare cases, the Persona Inquiry SDK can encounter client-side errors that cannot be resolved. When that happens, the SDK ends the flow and reports a typed PersonaError value to your app. The most common reasons include unrecoverable networking errors, misconfigured templates (should only be encountered during development), or failing to establish a connection to the device camera. PersonaError conforms to LocalizedError, so its errorDescription returns a human-readable string suitable for display or logging.

UIKit
SwiftUI

Handle errors in your InquiryDelegate.inquiryError(_:) implementation.

1extension MyViewController: InquiryDelegate {
2 func inquiryError(_ error: PersonaError) {
3 switch error {
4 case .networking:
5 // Show a retry prompt
6 case .misconfigured:
7 // Log a template-configuration error during development
8 default:
9 // Surface a generic error to the user
10 }
11 }
12}

Configuration

The builder API exposes a number of optional methods that let you tailor an inquiry to your integration — link it to a known user, pre-fill data, control which environment it runs in, and more. Chain any of the following on top of Inquiry.from(...) (or pass them through personaInquiry’s builder: closure in SwiftUI).

Linking to a Persona Account

accountId and referenceId are mutually exclusive. Setting an account ID clears any value previously set via referenceId.

If your integration uses Persona accounts to associate the same user across multiple inquiries, pass the account ID (prefixed with act_) to the inquiry.

UIKit
SwiftUI
1let inquiry = Inquiry.from(templateId: "itmpl_EXAMPLE", delegate: delegate)
2 .accountId("act_ABC123")
3 .build()

Linking by Reference ID

accountId and referenceId are mutually exclusive. Setting a reference ID clears any value previously set via accountId.

To make it easier to find Inquiries in the Persona Dashboard, we recommend passing in your system’s user ID for the Inquiry reference ID.

UIKit
SwiftUI
1let inquiry = Inquiry.from(templateId: "itmpl_EXAMPLE", delegate: delegate)
2 .referenceId("myUser_123")
3 .build()

Pre-writing Fields

If you want to add extra information to the Inquiry before the user even starts, you can pass them in as fields.

UIKit
SwiftUI
1let inquiry = Inquiry.from(templateId: "itmpl_EXAMPLE", delegate: delegate)
2 .fields([
3 "name_first": .string("Alexander"),
4 "name_last": .string("Example")
5 ])
6 .build()

Resuming an Inquiry

When you create an Inquiry on the server, you can pass the Inquiry ID instead of the Template ID.

UIKit
SwiftUI
1let inquiry = Inquiry.from(inquiryId: "inq_EXAMPLE", delegate: delegate)
2 .build()

If the Inquiry has already started, you will need to also pass in the session token.

UIKit
SwiftUI
1let inquiry = Inquiry.from(inquiryId: "inq_EXAMPLE", delegate: delegate)
2 .sessionToken("ABD1234567890")
3 .build()

Overriding Device Locale

Our SDK will automatically use the language and region selected on a users device to determine localization. If your app has specific localization requirements independent of user’s device settings, you can pass the localization directly to the inquiry as follows:

UIKit
SwiftUI
1let inquiry = Inquiry.from(templateId: "itmpl_EXAMPLE", delegate: delegate)
2 .locale("fr")
3 .build()

Selecting an Environment

By default, an inquiry runs in the .production environment. To start an inquiry in your sandbox while you develop, pass .sandbox instead. If you maintain multiple sandbox environments, you can also identify a specific one by ID.

UIKit
SwiftUI
1let inquiry = Inquiry.from(templateId: "itmpl_EXAMPLE", delegate: delegate)
2 .environment(.sandbox)
3 // Optional: pin to a specific environment by ID
4 // .environmentId("env_EXAMPLE")
5 .build()

Redirect URI

If your template uses a step that hands off to a browser or another app (for example, an external verification provider), the SDK uses the redirect URI to return the user to your app. Provide a URL whose scheme is registered for your app.

UIKit
SwiftUI
1let inquiry = Inquiry.from(templateId: "itmpl_EXAMPLE", delegate: delegate)
2 .redirectUri(URL(string: "your-app://persona-redirect"))
3 .build()

Sharing Data with a Share Token

If a user has already verified themselves with another organization on Persona and consents to reuse that data with you, you can redeem a share token (prefixed with cnst_) when starting an inquiry. The server pulls the previously verified fields, and the inquiry only asks the user for whatever is still missing.

UIKit
SwiftUI
1let inquiry = Inquiry.from(templateId: "itmpl_EXAMPLE", delegate: delegate)
2 .shareToken("cnst_EXAMPLE")
3 .build()

Customization

You can configure the styles that are applied to the inquiry template in the Persona Dashboard. For more information on using the theme editor, see our help article.

Theme Set

If you have multiple themes configured on the inquiry template, you can pick which one the SDK should use by passing its theme set ID (prefixed with thm_). If you don’t set a theme set ID, the template’s default theme is used.

UIKit
SwiftUI
1let inquiry = Inquiry.from(templateId: "itmpl_EXAMPLE", delegate: delegate)
2 .themeSetId("thm_ABC123")
3 .build()

Style Variant

By default, the SDK uses the light or dark variant of the active theme to match the device’s system appearance. Pass a StyleVariant to force a specific variant.

UIKit
SwiftUI
1let inquiry = Inquiry.from(templateId: "itmpl_EXAMPLE", delegate: delegate)
2 .styleVariant(.dark)
3 .build()

Custom Fonts

By default, the iOS SDK only has access to the device’s system font. Non system fonts can either be downloaded at runtime when uploaded to your inquiry template, or bundled into your hosting application.

Custom fonts that are not available in Persona themes are only available to customers on Enterprise plans.

Bundling a font

For example, if you choose the font ‘Rubik’ in your template’s Theme configuration, you will need to add a font file named Rubik.ttf (or any compatible format) to your project by following the instructions here.

If you need to use different font weights for a given family, name each font file such that the weight is appended to the end of the family name with a -. For example, a bold version of the Rubik font would be named Rubik-Bold.ttf. Valid font weight suffixes are Light, Regular, Medium, Bold, and ExtraBold.

Initial Loading Screen

The initial loading screen is shown after the inquiry is launched and before the first server response arrives. It is the only view in the SDK that is not configured by the server; every other screen is rendered from the theme set in the Persona Dashboard. You can replace the default loading animation with your own SwiftUI view to brand this moment.

Once the first server response arrives, subsequent loading screens use the theme configured in the Persona Dashboard and are not affected by this setting.

UIKit
SwiftUI
1let inquiry = Inquiry.from(templateId: "itmpl_EXAMPLE", delegate: delegate)
2 .initialLoadingView {
3 VStack(spacing: 16) {
4 ProgressView()
5 Text("Preparing your verification…")
6 }
7 }
8 .build()

Government Id NFC Integration

In order to use a template that includes Government Id NFC reading capabilities on iOS, follow these steps:

  • Include the PersonaNfc project in your app via SPM. You can include this in the same way you would the main Persona SDK. Make sure that the version of PersonaNfc matches the version of the main Persona SDK that you are using.
  • Link the PersonaOpenSSL library into your app using SPM.
  • Add the NFC capability to your app (target → signing & capabilities → + Capability → Near Field Communication Tag Reading). You will also need to add the NFC capability to the Identifier for the app in the Apple Developer portal.
  • Make sure that the entitlements file for your app includes both TAG and PACE for the Near Field Communication Tag Reader Session Formats:
    text
    <key>com.apple.developer.nfc.readersession.formats</key>
    <array>
    <string>TAG</string>
    <string>PACE</string>
    </array>
  • Add a Privacy - NFC Scan Usage Description to your info.plist file, along with a description.
  • Add a ISO7816 application identifiers for NFC Tag Reader Session to your info.plist file with these values in the following order: A0000002471001, A0000002472001, and 00000000000000.
  • Pass in PersonaNfcAdapter() into the Inquiry builder for the .nfcAdapter function. You will need to import PersonaNfc to access this.

Video Integration

In order to enable video recording over WebRTC on iOS follow these steps:

  • Include the PersonaWebRtc project in your app via SPM. You can include this in the same way you would the main Persona SDK. Make sure that the version of PersonaWebRtc matches the version of the main Persona SDK that you are using.
  • Link the WebRTC version 111.0.0 library into your app.
  • Pass in PersonaWebRtcAdapter() into the Inquiry builder for the .webRtcAdapter function. You will need to import PersonaWebRtc to access this.
  • Add a Privacy - Microphone Usage Description to your Info.plist file.

In order to enable local video recording upload on iOS follow these steps:

  • Add a Privacy - Microphone Usage Description to your Info.plist file.

Phone Number Silent Network Authentication (SNA) Integration

In order to use a template that includes phone number silent network authentication on iOS, follow these steps:

  • Include the PersonaSna project in your app via SPM. You can include this in the same way you would the main Persona SDK. Make sure that the version of PersonaSna matches the version of the main Persona SDK that you are using.
  • Pass in PersonaSnaAdapter() into the Inquiry builder for the .snaAdapter function. You will need to import PersonaSna to access this.

Licenses

The Persona iOS SDK is shipped with the licenses for the 3rd party software that it uses. Be sure to include these licenses in your app as well. See here for a list of the 3rd party software that we use and their associated licenses.