Fortface SDK Integration in Your iOS App

How does it work?

Our SDK is designed to capture or upload the document photo and send it to our Fortface API. To do this, there is a UIViewController that operates independently from your app, launched by an Engine when established, and automatically terminates after processing.

Our SDK does not communicate directly with the Fortface API; see the communication flow below:

Communication Flow

Below are the instructions for integrating our SDK, the process flow to correctly access our service, and a step-by-step guide on how to build an Engine.

Installation

Requirement

iOS: The minimum supported version is 13.0

Installation

Versions starting from 1.8

1. Import folder via local SPM

Download the file by clicking the link below and unzip it: Fortface.xcframework-1.9.zip

  1. In your project, go to Package Dependencies and click the + at the end of the Packages list.
  2. A new screen to add packages will open. In the bottom-right corner, click +.
  3. At the bottom of the screen, click Add local and select the folder you downloaded earlier.
  4. Select your project target and click Add Package.
  5. The new dependency should appear on the right side of your Xcode under Package Dependencies.

Versions prior to 1.8

1. Add the file to your project

First, you need to add the Fortface.framework to your project. Download it by clicking on the following link and unzip the file: Fortface.xcframework-1.9.zip.

To include the Fortface SDK dependency in your app, simply add the sdk.framework folder to your project.

Example:

your_project/Fortface.framework

ADD FOLDER PRINT HERE

Permission

To capture the photo, our SDK needs access to the device’s camera. Therefore, permission is requested on first access.

Follow these steps:

  1. In Xcode, in the project navigator (on the left), click the project icon (blue) to select it;
  2. In the main project view, under the "TARGETS" section, select your app;
  3. Go to the "Info" tab, in the "Custom iOS Target Properties" section, and in the last Key click the plus (+) sign to add a new property;
  4. Select "Privacy - Camera Usage Description";
  5. In the "Value" field, enter a message explaining why the app needs to access the camera. For example: "Camera access is required to capture photos and videos."

Example:

permission

Pre-initialization

Before starting the capture process, it is necessary to establish the device’s integrity to ensure secure communication with the Fortface API. Therefore, you must:

  1. Import our SDK into your app;
  2. Instantiate FortfaceSDK.

Establish Device Integrity

Initialize the SDK and obtain the deviceRequestInfo.

import Fortface

let deviceRequestInfo = FortfaceSDK.start(isDebugMode: true)
print(deviceRequestInfo)
// ztdTVLuGyQegvUnEtwAzcHbK8RoA9CXTuNRpCi9YXcwlzWpzOff+iOvUMkdprLk1E3npSKmk868x4pdJELztvRwu0auBrQdVFTu4wUI8ne35blqq1fds0ZHkOx8pfZGD4SIIPEVTEg2+uXQ8k6lxg7xoSnGzIPQJBZJmQWyDECXKDlXt+AAzMGN4lIWB2uM3ncwQLHPGDcGx52EgXBt3yQV7xZrCdinlwlDb21GK1kjygkRixqCvcB3LudlD8bYKKZq1PO5+faL0gFp6cTWpU9Fqkf7l9kbMc/5n+RGE83QzcQuLBzphDmIX2EUsqIdKFk/eg8WqAA9iC9YX4271/gt6RXqy2oQVJ4cyqD6bpLcRZtk/kLcW1lbo1h4bWyTyjC3XXnRPZVComA51s3Ouzxm4IxKX/EaTMd88n/ABiuE2s2DDKKKYc+WbqrDJo1btFHuXDA+O66mjrNKP+VE/CgtOrWF6W4x2cnpTNBJALmX23tion0caYxzbYpC18i3U

In FortfaceSDK, the start method takes one parameter and returns a string:

  • Parameter isDebugMode: a boolean that enables/disables debug mode. The default value is false;
    • Use true to enable debug mode;
    • Use false in production, so the device’s integrity is verified and, if unsuitable for transactions, a FortfaceException with the message There is something wrong! is thrown.
  • The return stored in deviceRequestInfo is a string used to validate the integrity of the device connected to our SDK. A new value is generated each time the method is called (the code above is an example).

With deviceRequestInfo in hand, you can now perform a handshake call between your back-end and the Fortface API.

The handshake is an important security measure to establish reliable communication between your back-end and the Fortface API, allowing verification of device authenticity, sharing of sensitive information (encryption keys and sessions), and establishing a secure data exchange channel.

Upon a successful handshake, you are assured that the device is secure and your app has the necessary information to proceed.

Capture and Document Upload

File Size Limit

The maximum file size (PDF/Image) for upload is 4MB. Larger files will be rejected with error code FileSizeTooBig.

Once device integrity is established, let’s see how to start the session and use the IFortfaceEngine interface to perform the capture.

Establish the Device Session

Implement a class that extends IFortfaceEngine.

class Engine: IFortfaceEngine {

init(sessionId: String, sessionKey: String, previousDoc: DocumentTypeEnum, documentList: [DocumentTypeEnum], openPhotoGalery: Bool) {
// For document capture
FortfaceManagerSession.startDocSessionEngine(
engine: self,
sessionId: sessionId,
sessionKey: sessionKey,
documentTypes: documentList,
previousDocType: previousDoc,
orientationManager: FortfaceOrientationManager()
)

// For document upload
FortfaceManagerSession.startUploadDocSessionEngine(
engine: self,
sessionId: sessionId,
sessionKey: sessionKey,
documentTypes: documentList,
openPhotoGalery: openPhotoGalery,
previousDocType: previousDoc
)
}

func finishedSessionEngine(sessionEngineResult: SessionEngineResult) {
switch sessionEngineResult {
case .capture:
captured(data: sessionEngineResult.data ?? Data())

case .cancel:
canceled()

case .timeout:
timeout()

case .deniedCamera:
cameraNotAuthorized()

case .error:
handleErrorAction(sessionDetails: sessionEngineResult.sessionDetails ?? Data())

default: return
}
}

private func captured(data: Data) {
let sdkData = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: String]
// Here the flow ends and these are the data to be sent to the Fortface API
}

private func canceled() {
// Here the user canceled the capture. You should proceed with your cancellation flow.
}

private func timeout() {
// Here the capture was canceled because time ran out. You should proceed with your timeout flow.
}

private func cameraNotAuthorized() {
// Here camera access was not granted by the user. You should proceed with your camera permission flow.
}

private func handleErrorAction(sessionDetails: Data) {
// Here we receive a JSON with the error type that occurred.
}
}

In this class, you initialize FortfaceManagerSession and its method startDocSessionEngine which takes 5 or 6 parameters:

  • engine: represents your Engine class that implements the IFortfaceEngine interface of our SDK. This will be explained further in the Creating an Engine section.
  • sessionId: the session id generated by the Fortface API;
  • sessionKey: the encryption key generated by the Fortface API;
  • documentTypes: represents the list of accepted documents. Document types are of DocumentTypeEnum and can be: RG_FRONT, CNH_FRONT, RG_BACK, CNH_BACK, UNDEFINED, DOC_NOT_FOUND, PASSPORT.
  • previousDocType: represents the type of document captured previously. When capturing or uploading a document for the first time, this parameter should be null. The value for this parameter is obtained from the response of the Fortface API service after sending the first side.
  • openPhotoGalery: used only for document upload. Receives a Bool indicating if the upload will be via the photo gallery or file gallery.
  • orientationManager: this class is responsible for determining the device's orientation.
  • getGeolocation: Define whether the SDK should collect location coordinates for anti-fraud reinforcement. (optional)

The startDocSessionEngine method, which currently accepts up to six parameters for document capture, will be discontinued. This method is already marked as obsolete (@Deprecated) and will stop functioning in future SDK releases.

The previousDocType and documentTypes parameters will be deprecated in upcoming versions.

class FortfaceOrientationManager: IFortfaceOrientationManager {
private func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.orientationLock = orientation
}
}

func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation: UIInterfaceOrientation) {
self.lockOrientation(orientation)
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
}
}

It is necessary to add this configuration in the AppDelegate of the integrating app so that the orientationManager can properly handle screen rotation. Currently, only the portrait orientation is supported during document capture.

import UIKit

class AppDelegate: UIResponder, UIApplicationDelegate {
(...)
var orientationLock = UIInterfaceOrientationMask.all
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return self.orientationLock
}
}

Mock data for example:

sessionId = "nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2k"

sessionKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2k+o81Oiti4TDezZYD8Q\nzj2RjO5f/YQHGmC1v7CWC4AJpD9+FOXBBO1pImaSg1gb8kOobRFGWbVdiHm35+PF\nTJceH31r+jKnvfc1TshsQVGtYLm7x5E/GFcjpGx9LdSxiXC6cHdCJf3o02/lytBa\nu8NCoH9FpPjyKrdGdSRb2haviAuEV4N1UBd5uZIsacVqZj52i1z+I94gLVKBZ/UV\npaxjLPWWngmaxbyKE0sEbt9JGleiCb2P1kZA9sU4L4Po7WBNk0+QOsvW9QjvS1+6\ntcZwwIDll2LyUceiFgxF6GkuFcHlBZDDtzymDDC2IYrf9/FNBOPeV1JHXON8T6DY\nAwIDAQAB\n-----END PUBLIC KEY-----\n"

Handling the sessionKey

The string that represents the sessionKey contains special characters (such as the line break \n) that must be preserved exactly as received from the Fortface API. Be careful with any accidental manipulation that may occur in:

  • Form inputs
  • Logs and monitoring systems
  • String processing in your backend
  • Transfers between services
  • Database storage

Any change to these characters may cause the key validation to fail. Make sure the key is passed to the SDK exactly as received from the Fortface API.

Development facilitation

For local frontend development only, you may reuse the same valid sessionKey as a mock as many times as necessary. The single-use restriction applies only in the context of backend development, when the Fortface API validates the key.

Understanding the code snippet:

  • The init calls FortfaceManagerSession.start
  • The session is created and the camera is rendered on the screen to capture the user’s document
    • Obs: in case of document upload, the gallery of files or photos is opened.
  • The data is encrypted and sent along with the captured photo to the Fortface API
  • The return occurs in the finishedSessionEngine callback
  • sessionEngineResult is the object that has three properties: data, action, and sessionDetails.
  • If sessionEngineResult.action returns capture, it means the flow proceeded as expected and the capture/upload data was returned.
  • data contains encrypted information and can be converted to JSON for easier handling. The keys are generated by the SDK, sent to your backend and then to the Fortface API.
{
key: String,
data: String,
imgData: String
}
  • If sessionEngineResult.action returns cancel, it means the user canceled the capture by tapping the Back button. In that case, proceed with the cancellation flow;

    • It is possible to hide the Back button and, consequently, not have a cancel action. See more details in the Customization section.
  • If sessionEngineResult.action returns timeout, it means that the capture time limit was reached. In that case, proceed with the timeout flow.

    • This action represents the time during which the SDK remains open, whether a capture occurs or not. When time runs out, the SDK terminates and the camera closes;
    • The default time is 20 seconds:
      • If you wish to keep it, no change is needed;
      • If you wish to change it, simply customize the value in seconds, see more details in the Customization section.
  • If sessionEngineResult.action returns deniedCamera, it means that the user did not grant camera access when the permission alert appeared. In that case, proceed with the camera permission flow.

  • If sessionEngineResult.action returns error, it means that an error occurred during the capture or upload of the document. In this case, you should follow the error flow.

    • The type of error is returned in the JSON sessionDetails that can be decoded using the SessionDetailsModel model. Today the errors received can be:
      • documentNotFound: code returned when the selected file (PDF/Image) cannot be found.
      • fileSizeTooBig: code returned when the selected file (PDF/Image) is larger than the allowed limit of 4MB.
      • fileCorrupted: code returned when the selected file (PDF/Image) is corrupted.

ATTENTION!!

The flow is executed for each “side” of the document, i.e., front and back. Each must go through the complete flow, including the Handshake and startDocSessionEngine. For capturing the second side, the document type (document side) recognized in the first capture must be provided; this information is available in the response from the Fortface API service. The JSON property referring to the document type is type, contained in the fortfaceSdk object.

Example response JSON from the Fortface service API for document capture:

{
"api":{
"timestamp":1741712439604,
"version":"v1.13"
},
"handshake":{
"sessionId":"187ffa8d-016d-c0962506f5ab",
"sessionKey":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2t6V+LTwyClMN/2Dy5W8\nrJKMAwP45QTq+8e3RcNowUqKEDrsjSkBDYugYCaEuLY66pP3ndm8MfenHxuxAqGC\n3NagAUr83+kJufsizuUJTYC2/1CXeox8U1j4S0Ewt/4rr9SIZCwBisiFiT0THt6QFjE\nmwWMYVT4RjRUK8LV6pF1HJvjFHI6oXF2KjjFwVqXur5LB7sj9f70ddcp3Fiv/3Tn\nVQIDAQAB\n-----END PUBLIC KEY-----\n"
},
"message":"",
"providers":{
"fortfaceSdk":{
"automatic":true,
"confidence":0.73,
"type":"CNH_FRONT"
},
"identify":{
"status":"PENDING"
},
"ocr":{
"status":"PENDING",
"type":"ocr"
}
},
"sdk":{
"brand":"APPLE",
"model":"IPHONE_14",
"platform":"IOS"
},
"title":""
}

Versioning

To know which version of our SDK is being used, simply call the function FortfaceSDK.getVersion() which will return a String with its value. Example:

let sdkVersion = FortfaceSDK.getVersion()
print(sdkVersion)
// 1.5.2

Callback for cancellation and timeout

When an attempt to capture the user's face is not completed successfully, for example when the user cancels the capture or when the capture does not happen during the set time, it is important that this event is sent to the backend so we have correct metrics for capture success or failure.

To allow us to get data from canceled or timed-out captures, our SDK has callbacks for the cancel and timeout actions.

The SessionEngineResult received in the finishedSessionEngine function is a Data object with two properties: data and action (the action performed in the SDK). Previously, the data field was only filled when face capture was completed.

As seen earlier in the Capture section, the data contains encrypted information and can be converted to JSON. However, unlike capture, in this scenario after converting to JSON, you will only have the following fields:

{
key: String,
data: String
}

Note: The keys key and data are generated by the SDK, sent to your backend and then to the Fortface API.

Implementation example

  1. The SDK detects that there was a capture cancellation.
  2. An encrypted object is created.
  3. The SDK ends and sends the payload and the action performed.
  4. In the implementing app, inside the class that extends IFortfaceEngine, it is detected that there was an action.
class Engine: IFortfaceEngine {

// ...

func finishedSessionEngine(sessionEngineResult: SessionEngineResult) {
switch sessionEngineResult {
case .capture:
captured(data: sessionEngineResult.data ?? Data())

// When the cancel action is detected, call the canceled function
// which receives the data generated by the SDK.
case .cancel:
canceled(data: sessionEngineResult.data ?? Data())

case .timeout:
timeout(data: sessionEngineResult.data ?? Data())

case .error:
handleErrorAction(sessionDetails: sessionEngineResult.sessionDetails ?? Data())

default: return
}
}

private func canceled(data: Data) {
// Send the cancellation payload to a metrics API
metricService(data: data)
}

private func metricService(data: Data) {
// Convert the Data object to JSON
if let metricRequest = try? JSONSerialization.jsonObject(with: data, options: [])
as? [String: String] {

// Transform into a Metric object
let request = getMetricRequest(sdkJsonDataRequest: metricRequest)

// Call the Service layer, sending the Metric to the API
serviceAPI.postSessionMetric(metricRequest: request){ response, error in }
}
}

private func getMetricRequest(sdkJsonDataRequest: [String: String]) -> Metric {
let sdkData = SdkData(
key: sdkJsonDataRequest["key"], data: sdkJsonDataRequest["data"], imgData: nil)
return Metric(sdkData: sdkData)
}

// ...
private func handleErrorAction(sessionDetails: Data) {
// Here we receive a JSON with the error type that occurred.
}
}
  1. Metric object sent to the API
import Foundation

struct Metric: Codable {
let sdkData: SdkData?
}
  1. SdkData object
import Foundation

struct SdkData: Codable {
let key: String?
let data: String?
let imgData: String?
}

Summary

  1. Check minimum requirements, add the framework, and authorize camera access;
  2. Capture the deviceRequestInfo;
  3. Perform the handshake;
  4. Start the Engine;
  5. Receive data in the callback;
  6. Send the data to the Fortface API;
  7. Receive the response from the Fortface API and proceed with your business logic.