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.
  • 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.7.0")
  ],
  // ...
)
# Add the below line to the Podfile
pod 'FingerprintPro', '~> 2.7'

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: "<<browserToken>>",
  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.

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.

Specifying linkedId and tag

Similar to the JavaScript browser SDK, the iOS SDK also supports providing custom metadata along with an identification request. To learn more about this capability, please see 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)
  }
}

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 error: 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: APIErrorType?
    /// A detailed error message.
    public let message: String
  }
}

/// An enumeration representing known Fingerprint API error types.
public enum APIErrorType: 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"
}

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 {
  /// 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?
}