iOS SDK

A simple guide to integrate Fingerprint's device intelligence platform in your native iOS apps.

In this guide, you will learn how to:

  • Include the SDK in your iOS apps.
  • Get a visitorId.
  • Read the SDK response.
  • Enable location data collection (optional; to start getting additional location-based proximity data).
  • Configure the SDK as per your needs.
  • Specify additional metadata in your identification request.
  • Handle errors.

For a complete example of how to use this SDK in your app, please visit the GitHub repo of our demo app.

Prerequisites

  1. Sign up for an account with Fingerprint to get your API key.
  2. Xcode 15.0 or higher. See App Store submission requirements for more information.

Including the SDK in your app

Add the SDK as a dependency.

// Add the below lines to Package.swift
let package = Package(
  // ...
  dependencies: [
    .package(url: "https://github.com/fingerprintjs/fingerprintjs-pro-ios", from: "2.0.0")
  ],
  // ...
)
# Add the below line to the Podfile
pod 'FingerprintPro', '~> 2.0'

Getting a visitorId

To get a visitorId, you need the public API key that you obtained when signing up for an account with Fingerprint. You can find this API key in the Dashboard > API Keys.

Here is an example that shows you how to get a visitorId:

import FingerprintPro

// Configure the SDK.
//
// The `region` must match the region associated with your API key.
let region: Region = .ap
//
// For custom subdomain or proxy integration, use `.custom(domain:fallback:)` region that lets
// you specify a custom endpoint.
// let region: Region = .custom(domain: "https://example.com")
//
// Additionally, you may also specify alternate, fallback endpoints to redirect failed requests 
// let region: Region = .custom(
//   domain: "https://example.com",
//   fallback: [
//     "https://one.fallback.com",
//     "https://two.fallback.com",
//   ]
// )
//
let configuration = Configuration(
  apiKey: "<<publicKey>>",
  region: region,
  extendedResponseFormat: true
)

// Initialize the SDK with the configuration created above.
let client = FingerprintProFactory.getInstance(configuration)

Task {
  do {
    // Get a `visitorId`.
    let visitorId = try await client.getVisitorId()
    print(visitorId)
  } catch {
    print(error.localizedDescription)
  }
}

Specifying a custom timeout

Default timeout value: 60 seconds

You can override the default timeout with a custom value of your choice. If the getVisitorId() call does not complete within the specified (or default) timeout, you will receive a FPJSError.clientTimeout error.

Here is an example that shows you how to specify a custom timeout:

Task {
  do {
    // Get a `visitorId` or, otherwise, time-out after 5 seconds.
    let visitorId = try await client.getVisitorId(timeout: 5.0)
    print(visitorId)
  } catch {
    print(error.localizedDescription)
  }
}

Reading the response

The function FingerprintClientProviding.getVisitorIdResponse() returns the response in a FingerprintResponse object.

Task {
  do {
    // Get a `sealedResult`.
    let response = try await client.getVisitorIdResponse()
    if let sealedResult = response.sealedResult {
      print(sealedResult)
    }
  } catch {
    print(error.localizedDescription)
  }
}

The FingerprintResponse object has the following fields, some of which can be empty/invalid when Sealed Client Results is enabled for your account.

FieldSealed Client Results is disabledSealed Client Results is enabled
versionString. The API version.String. The API version.
requestIdString. An identifier that uniquely identifies the request associated with the API call.String. An identifier that uniquely identifies the request associated with the API call.
visitorIdString. An identifier that uniquely identifies the device.N/A
confidenceFloat. A score that indicates the probability of this device being accurately identified. The value ranges from 0.0 to 1.0.N/A
visitorFoundBool. Indicates if a device has already been encountered by your app.N/A
sealedResultnilString?. An encrypted, binary, Base64-encoded String that contains the same response as you would receive with an /events API request.

Using Location Data for Proximity Detection

📘

This is optional. Enable this only if you want to include the additional location-based proximity detection signal in your response.

Starting the iOS SDK v2.10.0, new property allowUseOfLocationData has been added to the Configuration structure. This allows the Fingerprint client to access device location data when available. By default, it is set to false. To start getting Proximity ID and related data, set this property to true in your SDK configuration.

Important note: For the best location precision, we recommend initializing the Fingerprint client as early as possible - ideally during your app startup - and maintaining the same instance throughout the app’s lifecycle.

let configuration = Configuration(
  apiKey: "<<publicKey>>",
  region: region,
  extendedResponseFormat: true,
  allowUseOfLocationData: true
)

Handling Location Permissions

Your app is responsible for asking for location permissions. Refer to Apple documentation or check the Fingerprint demo app if you need to implement this flow. If you already have this process set up, skip to the next section. If your app doesn't currently implement a flow for requesting and managing location permissions, we've included a utility to help simplify this process.

The LocationPermissionHelper is designed to assist host apps with requesting, observing, and responding to user location permission changes. It leverages a LocationPermissionDelegate to notify the host app whenever the authorization status is updated. This simplifies the integration process without needing to build a full permission handling flow from scratch.

Here is an example that shows how to create a location permission helper:

// Create location permission helper.
let helper = FingerprintProFactory.getLocationPermissionHelperInstance()
helper.delegate = LocationPermissionDelegateImpl()

class LocationPermissionDelegateImpl: NSObject, LocationPermissionDelegate {
var authorizationStatus: CLAuthorizationStatus = .notDetermined

// Request permission or prompt to change settings.
func locationPermissionHelper(
    _ helper: LocationPermissionHelperProviding,
    shouldShowLocationRationale rationale: LocationPermissionRationale
) {
    switch rationale {
    case .needInitialPermission:
        helper.requestPermission()
    case .upgradeToPrecise:
        helper.requestPermanentPrecisePermission()
    case .needSettingsChange:
        helper.requestPermanentPrecisePermission()
    @unknown default:
        os_log("Received unknown rationale")
    }
}

// React to permission state changes.
  func locationPermissionHelper(
      _ helper: LocationPermissionHelperProviding,
      didUpdateLocationPermission status: CLAuthorizationStatus
  ) {
      authorizationStatus = status
  }
}

/// Represents reasons for showing a location permission rationale to the user.
public enum LocationPermissionRationale {

    /// The app has not yet requested any location permissions.
    /// A rationale should be shown before requesting for the first time.
    case needInitialPermission

    /// The user has denied or restricted location access.
    /// A rationale should be shown with instructions to change settings manually.
    case needSettingsChange

    /// The app has "When In Use" permission, but only reduced (approximate) accuracy is granted.
    /// A rationale should be shown encouraging the user to enable full (precise) accuracy via settings
    /// or give it temporarily.
    case upgradeToPrecise
}

Make sure to declare location permissions in Info.plist as it is required for App Store submission:

  • NSLocationWhenInUseUsageDescription - required; for general system request when asking for location access.
  • NSLocationTemporaryUsageDescriptionDictionarywith ProximityID purpose key - optional; for general system request when asking for temporary precise location in case the user has a setting that hides accurate location.
  • NSLocationAlwaysAndWhenInUseUsageDescription - optional, only if your app requires checking location in background mode; for general system request when asking to always have access to the user's location (including background). It will also require UIBackgroundModes to have location in Info.plist to access the user's location in the background.
  • Request permission in your app's code
    • Call requestPermission() or requestPrecisePermission() from the SDK, depending on the level of access needed.
    • Or use your own location permission flow.
  • Show rationale UI (optional)
    • You may present your own UI to explain why location is needed.

Configuring the SDK

Using the Configuration object, it is possible to configure the SDK as per your requirements. As of now, the following options are supported:

region

Type: Region.

Default value: Region.global

This option allows you to specify a region where you want your data to be stored and processed. This region must be the same as the one you specified when registering your app with Fingerprint. Use Region.custom(domain:fallback:) enumeration case to specify a custom endpoint, particularly when you have set up either a custom sub-domain or a proxy integration. See region for more information.

extendedResponseFormat

Type: Bool

Default value: false

When set to true, in addition to those fields returned by default, the FingerprintResponse object will also contain the following fields:

  • ipAddress - String. The IPv4 address of the device.
  • ipLocation - IPLocation. [This field is deprecated and will not return a result for applications created after January 23rd, 2024. See IP Geolocation for a replacement available in our Smart Signals product.] Indicates the location as deduced from the IP address.
  • firstSeenAt, lastSeenAt - Timestamp. See Visitor Footprint Timestamps.

allowUseOfLocationData

Type: Bool

Default value: false

This option allows you to enable the location data collection needed to calculate the location-based proximity detection signal.

Specifying linkedId and tag

Like the JS Agent, the iOS SDK also supports providing custom metadata with your identification request. To learn more about this capability, please visit Linking and tagging information.

Here is an example that shows you how to associate your identification request with an account ID and additional metadata:

let client = FingerprintProFactory.getInstance("<your-api-key>")

// Associate an account number to this request.
var metadata = Metadata(linkedId: "accountID")
// Associate additional metadata to this request.
metadata.setTag("purchase", forKey: "actionType")
metadata.setTag(10, forKey: "purchaseCount")

Task {
  // Get a `visitorId`.
  if let visitorId = try? await client.getVisitorId(metadata) {
    print(visitorId)
  }
}

The metadata you want to include may not be available when making the identification request. For such scenarios, you can update that event later when the required metadata becomes available. A typical workflow is explained in Update linkedId and tag on the server.

Handling errors

The SDK provides a FPJSError enumeration that helps you identify the reasons behind an unsuccessful identification request. Here is an example that shows you how to handle errors in your app:

do {
  let visitorId = try await client.getVisitorId()
  print(visitorId)
} catch FPJSError.networkError(let error) {
  print("Network error: \(error.localizedDescription)")
  // Handle network error (e.g. check Internet connection and try again)
} catch {
  print("Could not obtain visitor ID due to an error: \(error.localizedDescription)")
}

Data Classes

Configuration

/// A type that represents a single key/value parameter with the library integration
/// information.
public typealias IntegrationInfo = (String, String)

/// A library configuration specifying various options for configuring the Fingerprint Client
/// instance.
public struct Configuration: Sendable {
  /// A public API key obtained from [Fingerprint Dashboard](https://dashboard.fingerprint.com).
  public var apiKey: String
  /// The Fingerprint Server API region.
  public var region: Region
  /// An array of key/value parameters identifying a particular library integration.
  ///
  /// This property should only be used when creating an integration wrapper around the library
  /// (e.g. Flutter or React Native wrapper).
  public var integrationInfo: [IntegrationInfo]
  /// A Boolean value that determines whether the backend should respond with an extended
  /// result. See
  /// [developer documentation](https://dev.fingerprint.com/docs/ios-sdk#extendedresponseformat
  public var extendedResponseFormat: Bool

  /// Creates a library configuration object with the specified parameters.
  ///
  /// - Note: API keys are region-specific, so make sure you have selected the correct region
  ///         when calling this initializer.
  ///
  /// - Parameters:
  ///   - apiKey: A public API key obtained from 
  ///             [Fingerprint Dashboard](https://dashboard.fingerprint.com).
  ///   - region: The Fingerprint Server API region. Defaults to ``Region/global``.
  ///   - integrationInfo: An array of key/value parameters identifying a particular library
  ///                      integration. Defaults to an empty array.
  ///   - extendedResponseFormat: A Boolean value that determines whether the backend should 
  ///                             respond with an extended result. Defaults to `false`.
  public init(
      apiKey: String,
      region: Region = .global,
      integrationInfo: [IntegrationInfo] = [],
      extendedResponseFormat: Bool = false
  ) {
    self.apiKey = apiKey
    self.region = region
    self.integrationInfo = integrationInfo
    self.extendedResponseFormat = extendedResponseFormat
  }
}

Error

/// An error type that indicates problems with the device fingerprinting.
public enum FPJSError: Error {
  /// An error that indicates the server URL is invalid.
  case invalidURL
  /// An error that indicates the integration info URL parameters are invalid.
  case invalidURLParams
  /// An API error returned by the Fingerprint backend.
  case apiError(APIError)
  /// A network error.
  case networkError(Error)
  /// A JSON parsing error that indicates the request body is malformed.
  case jsonParsingError(Error)
  /// An error that indicates the response is invalid.
  case invalidResponseType
  /// An error that indicates the client session timed out.
  case clientTimeout
  /// Unknown error.
  case unknownError
}

/// A Fingerprint Server API error.
public struct APIError: Decodable, Sendable {
  /// The API version.
  public let version: String
  /// The identifier that uniquely identifies a request.
  public let requestId: String
  /// The error details.
  public let errorDetails: ErrorDetails?
}

public extension APIError {
  /// The details about a reason that the API request failed.
  struct ErrorDetails: Decodable {
    /// The error code, as defined by ``APIErrorType`` enum.
    public let code: APIError.Code?
    /// A detailed error message.
    public let message: String
  }

  /// An enumeration representing known Fingerprint API error codes.
  enum Code: String, Decodable, Sendable {
    /// The API token is missing.
    case tokenRequired = "TokenRequired"
    /// Invalid API token.
    case tokenNotFound = "TokenNotFound"
    /// The API token expired.
    case tokenExpired = "TokenExpired"
    /// Malformed request body or parameters.
    case requestCannotBeParsed = "RequestCannotBeParsed"
    /// Request failed.
    case failed = "Failed"
    /// A server connection timeout.
    case requestTimeout = "RequestTimeout"
    /// Request rate limit exceeded.
    case tooManyRequests = "TooManyRequests"
    /// The API key does not match a selected region.
    case wrongRegion = "WrongRegion"
    /// Subscription is not active for the provided API key.
    case subscriptionNotActive = "SubsriptionNotActive"
    /// This app is not authorized to make identification requests.
    case packageNotAuthorized = "PackageNotAuthorized"
    case originNotAvailable = "OriginNotAvailable"
    case headerRestricted = "HeaderRestricted"
    case notAvailableForCrawlBots = "NotAvailableForCrawlBots"
    case notAvailableWithoutUA = "NotAvailableWithoutUA"
    case unsupportedVersion = "UnsupportedVersion"
    case installationMethodRestricted = "InstallationMethodRestricted"
    case hostnameRestricted = "HostnameRestricted"
    case invalidProxyIntegrationSecret = "InvalidProxyIntegrationSecret"
    case invalidProxyIntegrationHeaders = "InvalidProxyIntegrationHeaders"
    case proxyIntegrationSecretEnvironmentMismatch = "ProxyIntegrationSecretEnvironmentMismatch”
  }
}

IPLocation

/// The IP address information.
public struct IPLocation: Equatable, Codable, Sendable {
  /// The city component of the IP address.
  public let city: IPGeoInfo?
  /// The country component of the IP address.
  public let country: IPGeoInfo?
  /// The continent component of the IP address.
  public let continent: IPGeoInfo?
  /// The longitude in degrees.
  public let longitude: Float?
  /// The latitude in degrees.
  public let latitude: Float?
  /// The postal code of the IP address.
  public let postalCode: String?
  /// The time zone of the IP address.
  public let timezone: String?
  /// The approximate accuracy radius in kilometers around the IP address location.
  public let accuracyRadius: UInt?
  /// The subdivisions (such as a county or other region) associated with the IP address.
  public let subdivisions: [IPLocationSubdivision]?
}

/// A structure containing the location name and code.
/// It can represent a country, city or continent.
public struct IPGeoInfo: Equatable, Codable, Sendable {
  /// The location name.
  public let name: String
  /// The area, country or continent code.
  public let code: String?
}

/// A structure describing the location's subdivision.
public struct IPLocationSubdivision: Equatable, Codable, Sendable {
  /// The ISO code.
  let isoCode: String
  /// The subdivision name.
  let name: String
}

Region

/// The Fingerprint Server API region.
public enum Region: Equatable, Sendable {
  /// A default Global (US) region.
  case global
  /// A European (EU) region.
  case eu
  /// An Asia-Pacific (APAC) region.
  case ap
  /// A custom endpoint with optional fallback endpoints, as defined by the `domain` and `fallback`
  /// associated values.
  case custom(domain: String, fallback: [String] = [])
}

Timestamp

/// A data structure representing the timestamp.
public struct SeenAt: Equatable, Codable, Sendable {
  /// The timestamp associated with the Fingerprint Server API region.
  public let global: Date?
  /// The timestamp associated with an active Fingerprint application.
  public let subscription: Date?
}