App Components

Integrations guide

App Components are standalone functionality modules for Android and iOS mobile apps. They capture images and data for you to upload via the API, leveraging Fourthline's data extraction AI services, and provide real-time feedback to the client. If you build custom user journeys, with your own UI, business logic, and orchestration, components reduce development time and effort.

Fourthline provides the UI for the components, which you can customize in the same way as the App Drop-in.
You can dynamically adjust functionality during flows, e.g. disable the tilted photo step.

Click to magnify

App Drop-in vs App Components

App Drop-in vs App Components


How it works

SDKs

We offer the following SDKs:

  • Android SDK and iOS SDK
  • Cordova, Flutter, and React Native plugins

Supported solutions

You can use App Components for the following solutions:

Flow

The flow is as follows:

Components flow

Components flow


Configuration

Error handling

You need to handle the following error values:

Android & iOS errors

Android & iOS errors

You need to handle the following error values:

ErrorDescription
CanceledThe client canceled the flow.
Action: Consider creating a new validationCode.
ClientRejectedThe client was rejected.
Action: You can't create a new workflow for this client.
ConfigurationNotSupportedThe workflowName created with the validationCode isn't supported.
Action: Consider updating your SDK to the latest version.
InvalidSessionThe WorkflowSession is invalid, e.g.
• "InvalidValidationCode": The validationCode is invalid.
• "SessionExpired": The WorkflowSession expired.
Action: Create a new validationCode.
InvalidWorkflowStatusThe workflow modules may have been completed successfully or finished with an error.
ModuleErrorThe client encountered an error in one of the workflow modules, e.g.:
IdentityVerification.DocumentExpired:
The client's ID document has expired.
IdentityVerification.DocumentTypeInvalid:
The MRZ of the ID document in the document photo is different from the document type selected by the client.
IdentityVerification.DocumentTypeMismatch:
The scanned document type must match the selected document type.
IdentityVerification.DocumentTypeNotSupported:
The client's document type isn't supported.
IdentityVerification.IssuingCountryNotSupported:
The issuing country of the client's ID document isn't supported.
IdentityVerification.NationalityNotSupported:
The client's nationality isn't supported.
IdentityVerification.NoDocumentDetected:
No ID document was detected in the document photos.
IdentityVerification.PersonNotAdult:
The client is underage.
UnexpectedAn unexpected error occurred.
Action: Immediately report this issue to Fourthline along with the message code.

Plugin errors

Plugin errors

The error is returned as a JSON string with the following format:

{
  "errorCode": Integer,
  "errorDescription": String
}

The following error codes and descriptions are possible:

Error codeError description
800Decoding Error
There was an error while decoding the workflow input.
Action: Check the errorDescription for more information.
802Invalid or missing font
The font wasn't found.
Action: Check the errorDescription for more information.
803User canceled
The client explicitly canceled the workflow.
830JSON parse error
We couldn't parse the json provided.
Action: Check the JSON provided.
850Incorrect Configuration
The workflow wasn't configured correctly.
Action: Check your workflow configuration.
870Unexpected + message
An unexpected occurred.
Action: Inform your implementation manager immediately and provide the message code.
1000ClientRejected
The client was rejected.
You can't retry.
1001InvalidSession
The current WorkflowSession is invalid, e.g.
• "InvalidValidationCode": The validationCode is invalid.
• "SessionExpired": The WorkflowSession expired.
Action: Create a new validationCode.
1002Module error + message
The client encountered an error in one of the workflow modules.
Action: For more information, check the message.
IdentityVerification.DocumentExpired:
The client's ID document has expired.
IdentityVerification.DocumentTypeInvalid:
The MRZ of the ID document in the document photo is different from the document type selected by the client.
IdentityVerification.DocumentTypeMismatch:
The scanned document type must match the selected document type.
IdentityVerification.DocumentTypeNotSupported:
The client's document type isn't supported.
IdentityVerification.IssuingCountryNotSupported:
The issuing country of the client's ID document isn't supported.
IdentityVerification.NationalityNotSupported:
The client's nationality isn't supported.
IdentityVerification.NoDocumentDetected:
No ID document was detected in the document photos.
IdentityVerification.PersonNotAdult:
The client is underage.
1003ConfigurationNotSupported + message
The workflow configuration created using the validationCode isn't supported.
Action: Consider updating to the latest plugin version.
Action: For more information, check the message.
1004InvalidWorkflowStatus
The workflow status is invalid.
Action: Check if the workflow module ended successfully or with an error.

iOS configuration

To ensure the client has granted camera and location permissions, in the Info.plist file, add Privacy - Camera Usage Description and Privacy - Location When In Use Usage Description entries.

Real-time feedback

The components can send document/selfie photos to our backend as soon as they are captured, where we assess the image quality and provide the client feedback in the UI in real time. The component displays an additional screen where we perform further validations.

This ensures higher-quality photos are ultimately uploaded, reduces sendbacks, and improves the user experience.

Agree with your implementation manager whether you want to enable real-time feedback for App Components.

UI customization

To customize each component's UI, create an OrcaFlavor object.

See App UI Customization.


Document Component

Use the Document Component to capture the client's ID document photos and video, based on your workflow configuration.

App Drop-in vs App Components

Document Component

Issuing country

The issuing country is only required for the client's primary ID document, not for Physical Proof of Address or tax documents.

When launching the Document Component, we check if you support the document type and issuing country.

Document types

The Document Component supports the following document types:

Document typeDescription
driversLicenseDriving license
dutchDriversLicenseDutch driving license
frenchIdCardFrench ID card
idCardID card
paperIdPaper ID
passportPassport
proofOfAddressProof of Address
residencePermitResidence permit
tinReferenceDocumentTIN document

Success handling

If the flow ends successfully, the component returns the following result:

  • A documentResult object containing the document photos, document video, and the data extracted from the MRZ
  • A documentAnalysis object containing the data extracted from the document photos

struct DocumentComponentResult {
 let documentResult: WorkflowResults.Component.Document
 let documentAnalysis: WorkflowResults.Component.DocumentAnalysis?
}
{
  "documentResult":{
    "documentType":"String",
    "images":[
      {
        "image":"String",
        "isAngled":"Boolean",
        "timestamp":"String",
        "fileSide":"String",
        "location":{
          "latitude":"String",
          "longitude":"Number"
        }
      }
    ],
    "mrtdMrzInfo":{
      "rawMrz":"String",
      "documentCode":"String",
      "issuingCountry":"String",
      "documentNumber":"String",
      "expirationDate":"String",
      "firstNames":[
        "String"
      ],
      "lastNames":[
        "String"
      ],
      "birthDate":"String",
      "nationality":"String",
      "gender":"String",
      "validationErrors":[
        "ValidationError"
      ]
    },
    "idlMrzInfo":{
      "rawMrz":"String",
      "documentNumber":"String",
      "validationErrors":[
        "ValidationError"
      ]
    },
    "videoRecording":{
      "url":"String",
      "duration":"String",
      "location":{
        "latitude":"String",
        "longitude":"Number"
      }
    }
  },
  "documentAnalysis":{
    "firstName":"String",
    "lastName":"String",
    "initials":"String",
    "gender":"String",
    "nationality":"String",
    "issuingCountry":"String",
    "issueDate":"String",
    "expirationDate":"String",
    "dateOfBirth":"String",
    "birthPlace":"String",
    "documentNumber":"String",
    "documentType":"String",
    "taxIdentificationNumber":"String"
  }
}
Plugins documentResult attributes

All attributes are optional.

AttributeDescription
documentType
String
The ID document type.
images
Array of objects
Information about each document photo.
images.image
String
The absolute filepath to the document photo.
images.isAngled
Boolean
True: The photo is tilted.
False: The photo is flat.
images.timestamp
String
The timestamp for when the document photo was captured.
images.fileSide
String
The side of the ID document.
images.location
Object
The coordinates of the document photo.
images.location.latitude
String
The latitude of the document photo.
Format: Float between -90 and 90
Example: 45.464664
images.location.longitude
String
The longitude of the document photo.
Format: Float between -90 and 90
Example: 45.464664
mrtdMrzInfo
Object
The data extracted from the MRZ of a frenchIdCard, idCard, passport, or residencePermit.
mrtdMrzInfo.rawMrz
String
The raw data from the MRZ.
mrtdMrzInfo.documentCode
String
[Is this document type?]
mrtdMrzInfo.issuingCountry
String
The country that issued the ID document.
mrtdMrzInfo.documentNumber
String
The ID document number.
mrtdMrzInfo.expirationDate
String
The ID document expiry date.
Format: Date YYYY-MM-DD
mrtdMrzInfo.firstNames
Array of strings
The client's first name(s).
Format: Alphabetical characters, spaces, hyphens, and apostrophes
mrtdMrzInfo.lastNames
Array of strings
The client's last name(s).
Format: Alphabetical characters, spaces, hyphens, and apostrophes
mrtdMrzInfo.birthDate
String
The client's date of birth.
Format: Date YYYY-MM-DD
mrtdMrzInfo.nationality
String
The client's nationality.
Format: ISO 3166-1 alpha-3 country code
mrtdMrzInfo.gender
String
The client's sex.
Female
Male
Other: ID document contains a value other than Female or Male
Unknown: ID document contains no gender field
mrtdMrzInfo.validationErrors
Array of strings [Or integers?]
The validation error code(s).
idlMrzInfo
Object
The data extracted from the MRZ of a Dutch driving license.
idlMrzInfo.rawMrz
String
The raw data from the MRZ.
idlMrzInfo.documentNumber
String
The ID document number.
idlMrzInfo.validationErrors
Array of strings
The validation error code(s).
videoRecording
Object
Information about the document video.
videoRecording.url
String
The absolute url filepath to the document video.
videoRecording.duration
String
The length of the document video. [What unit of time? Seconds?]
videoRecording.location
Object
The coordinates of the document video.
videoRecording.location.latitude
String
The latitude of the document video.
Format: Float between -90 and 90
Example: 45.464664
videoRecording.location.longitude
String
The longitude of the document video.
Format: Float between -90 and 90
Example: 45.464664

Plugins documentAnalysis attributes

All attributes are optional.

AttributeDescription
firstName
String
The client's first name.
Format: Alphabetical characters, spaces, hyphens, and apostrophes
lastName
String
The client's last name.
Format: Alphabetical characters, spaces, hyphens, and apostrophes
initials
String
The client's initials.
Format: Alphabetical characters, spaces, hyphens, and apostrophes
gender
String
The client's sex.
Female
Male
Other: ID document contains a value other than Female or Male
Unknown: ID document contains no gender field
nationality
String
The client's nationality.
Format: ISO 3166-1 alpha-3 country code
issuingCountry
String
The country that issued the ID document.
issueDate
String
The date the ID document was issued.
Format: Date YYYY-MM-DD
expirationDate
String
The date the ID document expires.
Format: Date YYYY-MM-DD
dateOfBirth
String
The client's date of birth.
Format: Date YYYY-MM-DD
birthPlace
String
The city where the client was born.
documentNumber
String
The ID document number.
documentType
String
The ID document type.
taxIdentificationNumber
String
The client's TIN.

Testing

Start a TestMe session using any mock validation code:

import com.fourthline.networking.NetworkEnvironment
import com.fourthline.orca.Orca
import com.fourthline.orca.workflow.WorkflowConfig
import com.fourthline.orca.workflow.WorkflowSession
import com.fourthline.orca.workflow.workflowSession 

fun startSession(context: Context) {
  val config = WorkflowConfig(networkEnvironment = NetworkEnvironment.Mock)

  Orca
    .workflowSession(
      context = context,
      validationCode = "IDV",
    )
    .configure(config)
    .start { result ->
      result.fold(
        onSuccess = { session ->
          launchDocumentComponent(context, session)
        },
        onFailure = { workflowError ->
          print("Handle error... $workflowError")
        }
      )
    }

}

fun launchDocumentComponent(context: Context, session: WorkflowSession) {
  val flavor = OrcaFlavor()
  val documentConfig = DocumentComponentConfig(
    type = documentType, /// See the `DocumentType` table.
    issuingCountry = issuingCountry /// Format: ISO3 country code
  )

  session
    .documentComponent(context, documentConfig)
    .customize(DocumentCustomizationConfig(flavor))
    .present { result ->
      result.fold(
        onSuccess = { documentResult ->
          print("Upload document result...")
        },
        onFailure = { workflowError ->
          print("Handle error... $workflowError")
        }
      )
    }
}
import FourthlineSDK

override func viewDidLoad() {
  super.viewDidLoad()
  
  let customization = WorkflowConfig(
    networkEnvironment: .mock
  )
  Orca
  .workflowSession(validationCode: "IDV")
  .configure(with: customization)
  .start { [weak self] result in
      switch result {
      case let .success(session):
         launchDocumentComponent(session)
      case let .failure(workflowError):
         print("Handle error...\(workflowError)")
      }
  }		
}

func launchDocumentComponent(_ session: WorkflowSession) {
   session
   .documentComponent(with:
       DocumentComponentConfig(
         type: documentType, /// See the `DocumentType` table.
         issuingCountry: issuingCountry /// Format: ISO3 country code
       )
    )
   .customize(with: DocumentCustomizationConfig(flavor: orcaFlavor)
   .present { [weak self] result in
      switch result {
      case let .success(documentResult):
         print("Upload document result...")
      case let .failure(workflowError):
         print("Handle error...\(workflowError)")
      }
   }
}
function startWorkflowSession() {
  var config = `{
    "configuration": {
      "validationCode": "IDV",
      "networkEnvironment": "mock"
    }
  }`;
    
    
  FourthlinePlugin.startWorkflowSession(
    config,
    function(msg) {
       // The client has successfully started a workflow session.
       // You can now start a workflow component.
    },
    function(error) {
       // Extract and process information from the error.
       // See Error handling.
       
       var jsonError = JSON.parse(error.message);
    }
  );
}

function startWorkflowDocumentComponent() {
  var config = `{
    "configuration": {
      "documentType": "passport",
      "issuingCountry": "NLD"
    },
  }`;

  FourthlinePlugin.startWorkflowComponentDocument(
    config,
    function(msg) {
       // Use the componentResult to continue the user experience on your side.
    },
    function(error) {
       // Extract and process information from the error.
       // See Error handling.
       
       var jsonError = JSON.parse(error.message);
    }
  );
}

final _fourthlinePlugin = Fourthline();

startWorkflowSession() async {
  String config = """
    {
      "configuration": {
        "validationCode": "IDV",
        "networkEnvironment": "mock"
      }
    }
    """;

  try {
    String sessionResult = await _fourthlinePlugin.startWorkflowSession(config) ?? "Could not start Workflow Session";
    // The client has successfully started a workflow session.
    // You can now start a workflow component.
    startWorkflowComponent();
  } on PlatformException catch (e) {
    // Extract and process information from the error.
    // See Error handling.
  };
}

startWorkflowComponent() async {
  String config = """
    {
      "configuration": {
        "documentType": "passport",
        "issuingCountry": "NLD"
      },
    }
    """;

  try {
    String componentResult = await _fourthlinePlugin.startWorkflowComponentDocument(config) ?? "Could not start Workflow Document Component";
    // Use the componentResult to continue the user experience on your side.
  } on PlatformException catch (e) {
    // Extract and process information from the error.
    // See Error handling.
  };
}
function startWorkflowSession() {
  var config = `
    {
      "configuration": {
        "validationCode": "IDV",
        "networkEnvironment": "mock"
      }
    }
  `;

  NativeModules.Fourthline.startWorkflowSession(config)
       .then((result) => {
            // The client has successfully started a workflow session.
            // You can now start a workflow component.
	    startWorkflowComponent();
       })
       .catch((error) => {
            // Extract and process information from the error.
   	    // See Error handling.
       });
}


startWorkflowComponent() async {
  var config = `
    {
      "configuration": {
        "documentType": "passport",
        "issuingCountry": "NLD"
      },
    }
  `;

  NativeModules.Fourthline.startWorkflowComponentDocument(config)
       .then((result) => {
            // Use the componentResult to continue the user experience on your side.
       })
       .catch((error) => {
            // Extract and process information from the error.
            // See Error handling.
       });
}

Example

The following is a complete example:

import com.fourthline.networking.NetworkEnvironment
import com.fourthline.orca.Orca
import com.fourthline.orca.core.flavor.OrcaFlavor
import com.fourthline.orca.core.flavor.OrcaFonts
import com.fourthline.orca.document.DocumentCustomizationConfig
import com.fourthline.orca.workflow.DocumentComponentConfig
import com.fourthline.orca.workflow.WorkflowConfig
import com.fourthline.orca.workflow.WorkflowSession
import com.fourthline.orca.workflow.workflowSession
import java.io.File

fun startSession(context: Context) {
    // Make a POST Create SDK session request: https://{{baseUrl}}/v1/workflows/{{workflowId}}/validationcode.
    val validationCode = "xxxxxxxx"

    // To test the workflow offline, use Mock. To test networking, use Sandbox or Production.
    val config = WorkflowConfig(networkEnvironment = NetworkEnvironment.Mock)

    Orca
      .workflowSession(
        context = context,
        validationCode = validationCode,
      )
      .configure(config)
      .start { result ->
        result.fold(
          onSuccess = { session ->
            launchDocumentComponent(context, session)
          },
          onFailure = { workflowError ->
            print("Handle error... $workflowError")
          }
        )
      }
  }

  fun launchDocumentComponent(context: Context, session: WorkflowSession) {
    val customFlavor = OrcaFlavor(
      fonts = OrcaFonts(
        screenHeader = OrcaFonts.Font.FromFontRes(fontRes = R.font.roboto_medium, size = 20),
        primaryButton = OrcaFonts.Font.FromFile(file = File(...), size = 18
      ),
    )

    val documentConfig = DocumentComponentConfig(
      type = documentType, /// See the `DocumentType` table.
      issuingCountry = issuingCountry /// Format: ISO3 country code
    )

    session
      .documentComponent(context, documentConfig)
      .customize(DocumentCustomizationConfig(customFlavor))
      .present { result ->
        result.fold(
          onSuccess = { documentResult ->
            print("Upload document result...")
          },
          onFailure = { workflowError ->
            print("Handle error... $workflowError")
          }
        )
      }
  }
import FourthlineSDK

override func viewDidLoad() {
  super.viewDidLoad()
  
  let customization = WorkflowConfig(
    networkEnvironment: .mock // To test the workflow offline, use .mock. To test networking, use .sandbox or .production.
  )

  let validationCode = "xxxxxxxx" // Make a POST Create SDK session request: https://{{baseUrl}}/v1/workflows/{{workflowId}}/validationcode.
  
  Orca
  .workflowSession(validationCode: validationCode)
  .configure(with: customization)
  .start { [weak self] result in
     switch result {
     case let .success(session):
       launchDocumentComponent(session)
     case let .failure(workflowError):
       print("Handle error...\(workflowError)")
    }
  }		
}

func launchDocumentComponent(_ session: WorkflowSession) {
  var orcaFlavor = OrcaFlavor()
  
  // Configure the colors
  var palette = OrcaPalette()
  palette.primary = UIColor(red: 82.0 / 255.0, green: 30.0 / 255.0, blue: 135.0 / 255.0, alpha: 1)
  
  var colors = OrcaColors(colorPalette: palette)
  colors.hint.backgroundColor = UIColor(red: 221.0 / 255.0, green: 210.0 / 255.0, blue: 232.0 / 255.0, alpha: 0.5)
  colors.box.borderColor = UIColor.gray
  colors.screen.tableCells.cellStyle2.iconColor = UIColor(red: 221.0 / 255.0, green: 210.0 / 255.0, blue: 3.0 / 255.0, alpha: 1)
  orcaFlavor.colors = colors

  let documentType = .passport 
  let issuingCountry = "NLD"

  session
  .documentComponent(with:
     DocumentComponentConfig(
        type: documentType,
        issuingCountry: issuingCountry
     )
  )
  .customize(with: DocumentCustomizationConfig(flavor: orcaFlavor)
  .present { [weak self] result in
     switch result {
     case let .success(documentResult):
       print("Upload document result...")
     case let .failure(workflowError):
       print("Handle error...\(workflowError)")
     }
  }
}
// Call this after starting a valid WorkflowSession.
function startWorkflowDocumentComponent() {
  var config = `{
    "configuration": {
      "documentType": "passport",
      "issuingCountry": "NLD"
    },
    "customization": {
      ...
    }
  }`;

  FourthlinePlugin.startWorkflowComponentDocument(
    config,
    function(msg) {
       // Use the componentResult to continue the user experience on your side.
    },
    function(error) {
       // Extract and process information from the error.
       // See Error handling.
       
       var jsonError = JSON.parse(error.message);
    }
  );
}

final _fourthlinePlugin = Fourthline();

// Call this after starting a valid WorkflowSession.
startWorkflowComponent() async {
  String config = """
    {
      "configuration": {
        "documentType": "passport",
        "issuingCountry": "NLD"
      },
      "customization": {
        ...
      }
    }
    """;

  try {
    String componentResult = await _fourthlinePlugin.startWorkflowComponentDocument(config) ?? "Could not start Workflow Document Component";
    // Use the componentResult to continue the user experience on your side.
  } on PlatformException catch (e) {
    // Extract and process information from the error.
    // See Error handling.
  };
}
// Call this after starting a valid WorkflowSession.
startWorkflowComponent() async {
  String config = `
    {
      "configuration": {
        "documentType": "passport",
        "issuingCountry": "NLD"
      },
      "customization": {
       	"flavor": {
     	    "colors": ${orcaColors},
            "fonts": ${orcaFonts},
            "localization": ${orcaLocalization},
            "layouts": ${orcaLayout}
        }
     }
  }
  `;

  NativeModules.Fourthline.startWorkflowComponentDocument(config)
       .then((result) => {
            // Use the componentResult to continue the user experience on your side.
       })
       .catch((error) => {
            // Extract and process information from the error.
            // See Error handling.
       });
}


Biometrics Component

Use the Biometrics Component to capture the selfie photo and video, based on your workflow configuration.

Biometrics Component

Biometrics Component

Success handling

If the flow ends successfully, the component returns the following result:

  • A selfie object containing the selfie photo, timestamp, and location (if available)
  • A liveness object containing the selfie video (if configured)

data class BiometricsComponentResult(
    val selfie: WorkflowResults.IDV.Selfie,
    val liveness: WorkflowResults.IDV.SelfieVideo?,
)
{
  "selfie":{
    "image":"String",
    "timestamp":"String",
    "location":{
      "latitude":"String",
      "longitude":"Number"
    }
  },
  "liveness":{
    "url":"String",
    "duration":"String",
    "location":{
      "latitude":"String",
      "longitude":"Number"
    }
  }
}
Plugins selfie attributes

All attributes are optional.

AttributeDescription
image
String
The absolute url filepath to the selfie photo.
location
Object
The coordinates of the selfie photo.
location.latitude
String
The latitude of the selfie photo.
Format: Float between -90 and 90
Example: 45.464664
location.longitude
String
The longitude of the selfie photo.
Format: Float between -90 and 90
Example: 45.464664

Plugins liveness attributes

All attributes are optional.

AttributeDescription
url
String
The absolute url filepath to the selfie video.
duration
String
The length of the selfie video. [What unit of time? Seconds?]
location
Object
The coordinates of the selfie video.
location.latitude
String
The latitude of the selfie video.
Format: Float between -90 and 90
Example: 45.464664
location.longitude
String
The longitude of the selfie video.
Format: Float between -90 and 90
Example: 45.464664

Testing

Start a TestMe session using any mock validation code:

fun launchBiometricsComponent(context: Context, session: WorkflowSession) {
    val customFlavor = OrcaFlavor(
      fonts = OrcaFonts(
        screenHeader = OrcaFonts.Font.FromFontRes(fontRes = R.font.roboto_medium, size = 20),
        primaryButton = OrcaFonts.Font.FromFile(file = File(...), size = 18
      ),
    )

    session
        .biometricsComponent(context)
        .customize(SelfieCustomizationConfig(customFlavor))
        .present { result ->
            result.fold(
                onSuccess = { biometricsResult ->
                    print("Upload biometrics result...")
                },
                onFailure = { workflowError ->
                    print("Handle error... $workflowError")
                }
            )
        }
}
import FourthlineSDK

override func viewDidLoad() {
  super.viewDidLoad()
  
  let customization = WorkflowConfig(
    networkEnvironment: .mock
  )
  Orca
  .workflowSession(validationCode: "IDV")
  .configure(with: customization)
  .start { [weak self] result in
      switch result {
      case let .success(session):
         launchBiometricsComponent(session)
      case let .failure(workflowError):
         print("Handle error...\(workflowError)")
      }
  }		
}

func launchBiometricsComponent(_ session: WorkflowSession) {
   session
   .biometricsComponent()
   .present { [weak self] result in
      switch result {
      case let .success(biometricsResult):
	     // Use the componentResult to continue the user experience on your side.
         print("Upload biometrics result...")
      case let .failure(workflowError):
         print("Handle error...\(workflowError)")
      }
   }
}
function startWorkflowSession() {
  var config = `{
    "configuration": {
      "validationCode": "IDV",
      "networkEnvironment": "mock"
    }
  }`;
    
    
  FourthlinePlugin.startWorkflowSession(
    config,
    function(msg) {
       // User has successfully started a WorkflowSession.
       // You can now start a Workflow Component.
    },
    function(error) {
       // Extract and process information from the error.
       // Please read the `Workflow error output` section.
       
       var jsonError = JSON.parse(error.message);
    }
  );
}

function startWorkflowBiometricsComponent() {
  Fourthline.isWorkflowSessionAvailable(
          function(msg) { 
            if (msg == "true") {
              Fourthline.startWorkflowComponentBiometrics(config,
                function(msg) {
                  ...
                },
                function(err) {
                  ...
                }
              );
            } else {
              ...
            }
          }
  )
}
final _fourthlinePlugin = Fourthline();

startWorkflowSession() async {
  String config = """
    {
      "configuration": {
        "validationCode": "IDV",
        "networkEnvironment": "mock"
      }
    }
    """;

  try {
    String sessionResult = await _fourthlinePlugin.startWorkflowSession(config) ?? "Could not start Workflow Session";
    // User has successfully started a WorkflowSession.
    // You can now start a Workflow Component.
    startWorkflowComponent();
  } on PlatformException catch (e) {
    // Extract and process information from the error.
    // Please read the `Workflow error output` section.
  };
}

fun startWorkflowComponent() async {
  String config = """
    {
      "customization": {
      ...
      },
    }
    """;

  try {
    String componentResult = await _fourthlinePlugin.startWorkflowComponentBiometrics(config) ?? "Could not start Workflow Biometrics Component";
    // Use the componentResult to continue the user experience on your side.
  } on PlatformException catch (e) {
    // Extract and process information from the error.
    // Please read the `Workflow error output` section.
  };
}
function startWorkflowSession() {
  var config = `
    {
      "configuration": {
        "validationCode": "IDV",
        "networkEnvironment": "mock"
      }
    }
  `;

  NativeModules.Fourthline.startWorkflowSession(config)
       .then((result) => {
            // User has successfully started a WorkflowSession.
            // You can now start a Workflow Component.
		    startBiometricsComponent();
       })
       .catch((error) => {
            // Extract and process information from the error.
   	     // Please read the `Workflow error output` section.
       });
}

function startBiometricsComponent() {
   NativeModules.Fourthline.isWorkflowSessionAvailable((error, isAvailable) => {
      if (isAvailable) {
        var config = `{}`;
        NativeModules.Fourthline.startWorkflowComponentBiometrics(config)
          .then((result) => {
            // Use the componentResult to continue the user experience on your side.
          })
          .catch((error) => {
            // Extract and process information from the error.
            // Please read the `Error output` section.
          });
      } else {
        showResult('Please start workflow session first.');
      }
   });
}

Example

The following is a complete example:

 private fun startBiometricsComponent(workflowSession: WorkflowSession) {
        workflowSession
                .biometricsComponent(context)
                .customize(
                    SelfieCustomizationConfig(
                        flavor = OrcaFlavor(),
                    )
                )
                .present { workflowResult ->
                    workflowResult.fold(
                        onSuccess = {
                            // handle success
                        },
                        onFailure = { error ->
                            // handle error
                        },
                    )
                }
        }
    }
import FourthlineSDK

override func viewDidLoad() {
  super.viewDidLoad()
  
  let customization = WorkflowConfig(
    networkEnvironment: .mock // choose .mock to test Workflow offline or .sandbox/.production to test networking 
  )

  let validationCode = "xxxxxxxx" // generated by calling POST https://{{baseUrl}}/v1/workflows/{{workflowId}}/validationcode
  
  Orca
  .workflowSession(validationCode: validationCode)
  .configure(with: customization)
  .start { [weak self] result in
     switch result {
     case let .success(session):
       launchBiometricsComponent(session)
     case let .failure(workflowError):
       print("Handle error...\(workflowError)")
    }
  }		
}

func launchBiometricsComponent(_ session: WorkflowSession) {
  var orcaFlavor = OrcaFlavor()
  
  // Configure the colors
  var palette = OrcaPalette()
  palette.primary = UIColor(red: 82.0 / 255.0, green: 30.0 / 255.0, blue: 135.0 / 255.0, alpha: 1)
  
  var colors = OrcaColors(colorPalette: palette)
  colors.hint.backgroundColor = UIColor(red: 221.0 / 255.0, green: 210.0 / 255.0, blue: 232.0 / 255.0, alpha: 0.5)
  colors.box.borderColor = UIColor.gray
  colors.screen.tableCells.cellStyle2.iconColor = UIColor(red: 221.0 / 255.0, green: 210.0 / 255.0, blue: 3.0 / 255.0, alpha: 1)
  orcaFlavor.colors = colors

   session
   .biometricsComponent()
   .customize(with: SelfieCustomizationConfig(flavor: orcaFlavor)
   .present { [weak self] result in
      switch result {
      case let .success(biometricsResult):
	     // Use the componentResult to continue the user experience on your side.
         print("Upload biometrics result...")
      case let .failure(workflowError):
         print("Handle error...\(workflowError)")
      }
   }
}
function startWorkflowBiometricsComponent() {
  Fourthline.isWorkflowSessionAvailable(
    function(msg) {
        if (msg == "true") {
            Fourthline.startWorkflowComponentBiometrics(
              config,
              function(msg) {
                // handle success
              },
              function(err) {
                // handle error
              });
        } else {
            // start the workflow session first
        }
    }
  )
}
 Future<void> startBiometricsComponent() async {
    try {
      if (await _fourthlinePlugin.isWorkflowSessionAvailable() ?? false) {
        String result = await _fourthlinePlugin.startWorkflowComponentBiometrics(inputJson.value.text) ?? "";
        // handle success
      } else {
        // Please start workflow session first
      }
    } on Exception catch (e) {
      // handle error
    }
  }
// Call this after starting a valid WorkflowSession.
startWorkflowComponent() async {
  String config = `
    {
      "customization": {
       	"flavor": {
     	    "colors": ${orcaColors},
            "fonts": ${orcaFonts},
            "localization": ${orcaLocalization},
            "layouts": ${orcaLayout}
        }
     }
  }
  `;

 NativeModules.Fourthline.isWorkflowSessionAvailable((error, isAvailable) => {
      if (isAvailable) {
        var config = `{}`;
        NativeModules.Fourthline.startWorkflowComponentBiometrics(config)
          .then((result) => {
            // Use the componentResult to continue the user experience on your side.
          })
          .catch((error) => {
            // Extract and process information from the error.
            // Please read the `Error output` section.
          });
      } else {
        showResult('Please start workflow session first.');
      }
   });
}


Plugin helpers

Clear workflow session

To delete any workflow resources after a component finishes and clear the workflow session, use the clearWorkflowSession function.

Before launching the component, call startWorkflowSession.

Fourthline.clearWorkflowSession(
  function(msg) {
    ...
  });
await _fourthlinePlugin.clearWorkflowSession()
NativeModules.Fourthline.clearWorkflowSession((error, success) => {});

Workflow session availability

To confirm that the workflow session is available before launching a component, use the isWorkflowSessionAvailable function.

Before launching the component, call startWorkflowSession.

Fourthline.isWorkflowSessionAvailable(
  function(msg) {
      if (msg == "true") {
      ...
      } else {
      ...
      }
)
if (await _fourthlinePlugin.isWorkflowSessionAvailable() ?? false) {
  ...
}
NativeModules.Fourthline.isWorkflowSessionAvailable((error, isAvailable) => {});


Success
You have set up App Components!
To integrate your solutions, go the Integration Guides section and follow the API guide for the relevant solutions.

Top of page

Accordion in HTML5