# The Integration Guide

Footprint makes it easy to onboard your users, whether you need KYC, KYB,
identity document verification, or document collection. Security, compliance,
and risk/fraud prevention come bundled in. This is the definitive guide to
integrating Footprint into your product, including the advanced options and
features that let you fully customize and own your flow.

# Core concepts

### Vault

Each entity in Footprint is backed by a secure data **Vault** that stores
identity, financial, documents, and custom data attributes. When a user onboards
via a Footprint flow all data collected is automatically stored in the user’s
vault. Vaults store both structured and unstructured data, and keep track of
each change to every attribute with a versioned history. Footprint's vaulting
aims to be flexible while providing base-level validation logic for structured
vault data such as Identity or PCI data. All attribute values are referenced by
specific **data identifiers** (such as `id.ssn9` references the full SSN of a
user). Vault data fields are a core part of the Footprint platform: they support
granular role-based access controls (RBACs) for data access and can be used to
transmit data securely (via the Vault Proxy) to third-party destinations. Read
more about all the
[vault data fields here](/articles/vault/fields).

### The "fp\_id"

Also known as the “Footprint ID”. This is the unique, per-user identifier for an
entity inside of Footprint. It appears in API request, responses, and on the
dashboard to uniquely identify an entity. Similar to `fp_id`, an `fp_bid` is the
unique identifier for a business entity.

### The "external\_id"

This is an identifier that **you provide** for a user or business. This provides
a mechanism which you can map entities in your system to Footprint without
storing/knowing the Footprint ID for that entity.

We strongly recommend maintaining a one-to-one
mapping from users in your own database to users in Footprint. One easy way to
guarantee this is to provide an external ID when creating users.

### Playbook

A Playbook defines the end-to-end onboarding flow powered by Footprint: (a) what
information needs to be collected, (b) what verification checks need to run, and
(c) the rules that define decisioning based on the verification checks. A
`playbook_key` is the unique, publishable identifier for a playbook and appears
in API requests, responses, and in the dashboard.

### Onboarding

An onboarding represents a user/business onboarding onto a Playbook. Each time a
user/business goes through a playbook, it creates a new onboarding.

# Integration overview

A user's journey with Footprint starts via the
POST
/onboardings API. It runs one of your playbooks against a user
or business and returns the result. How much of the flow runs headlessly depends
on how much data you've already collected:

1. **Fully headless:** if your application already collects all the data the
   playbook needs, vault that data and run the onboarding entirely server-side,
   with no Footprint UI shown at all. The decision comes back synchronously
   inline or asynchronously via webhooks, depending on your settings.
2. **Interactive:** if there is still information to collect from the user, the
   API returns a `continue_onboarding` token and link. Hand these to your
   frontend so the user can finish the remaining steps in one of two ways:
   * **Hosted:** a web page hosted by Footprint that includes the rest of the
     onboarding flow. Send the `link` to your user via email, SMS, or a button
     in your app.
   * **Embedded:** use one of our many SDKs across web, iOS, and Android to
     embed the rest of the flow inside your product, launched with the `token`.

### Customization

The interactive flow supports customization to varying degrees:

1. **Hosted:** Coming soon, use our appearance editor in the dashboard to
   control every element just like in the embedded flow.
2. **Embedded:** use our SDK to fully customize the look and feel of each
   element including fonts, colors, borders, and much more. We support over 100
   attributes of customization. See here for full details:
   [https://docs.onefootprint.com/articles/integrate/customization](/articles/integrate/customization).

### Identity documents & document collection

One important caveat: Footprint handles the complexity of document collection
and verification. Document collection is considered a singular component which
includes everything from: device handoff (leveraging a mobile device when
starting the flow on desktop), automatic capture and liveness, document
classification, and selfie capture. The customization options extend to the full
document collection and scanning experience.

# The end-to-end integration

This guide covers all the most common steps to getting Footprint fully up and
running in just a few minutes.

## Step 1: Get your secret API key

Go to [the Footprint dashboard](https://dashboard.onefootprint.com/api-keys) and
**create a secret key**. Store this key somewhere safe.

## Step 2: Create a playbook

Go to [the Footprint dashboard](https://dashboard.onefootprint.com/playbooks)
and create a Playbook. Playbooks define the onboarding: (a) what information
needs to be collected, (b) what verification checks need to run, and (c) the
rules that define decisioning based on the verification checks.

## Step 3: Create the user and vault their data

Create the user with
POST
/users (or a business with
POST
/businesses) and write whatever data you have already
collected:

```bash
curl -X POST https://api.onefootprint.com/users \
  -u <SECRET_API_KEY>: \
  -d '{
    "id.first_name": "Jane",
    "id.last_name": "Doe",
    "id.dob": "1990-01-01",
    "id.ssn9": "123-45-6789",
    "id.address_line1": "1 Main St",
    "id.city": "San Francisco",
    "id.state": "CA",
    "id.zip": "94105",
    "id.country": "US"
  }'
# -> { "id": "fp_id_K0q6Eh6Rr3WOOfFBLPiHsr" }
```

See [Vault fields](/articles/vault/fields) for the full list of fields you can
vault, and [Migrating user data](/articles/integrate/migrate-existing-data)
to bring over data you already hold. If you expect the user to provide most of
their information through the interactive flow, it's fine to vault only what you
have, or nothing at all.

To keep a one-to-one mapping with your own records,
pass your own identifier as the `x-external-id` header when creating the user.
You can then reference the user by `external_id` in later API calls instead of
storing the `fp_id`.

## Step 4: Run the playbook

Call POST
/onboardings with the entity and the playbook key. Set
`synchronous_timeout_secs` (max 30) to wait for the decision and receive it
inline:

```bash
curl -X POST https://api.onefootprint.com/onboardings \
  -u <SECRET_API_KEY>: \
  -d '{
    "fp_id": "fp_id_K0q6Eh6Rr3WOOfFBLPiHsr",
    "key": "<PLAYBOOK_KEY>",
    "synchronous_timeout_secs": 30
  }'
```

```json
{
  "id": "ob_SRFT2a1mN7DAWJ0VPXkiqK",
  "status": "pass",
  "requires_manual_review": false,
  "continue_onboarding": null,
  "error_message": null
}
```

If you omit `synchronous_timeout_secs`, the call returns immediately with
`status: "pending"` and the run continues in the background; the final decision
arrives via webhook (step 6). You'll generally only run a playbook
asynchronously when you have already collected all the information it needs.
This mode is useful for latent operations, like running an AI agent, that may
not finish within the synchronous timeout.

For a business, put the `fp_bid` in the `fp_id` field; there is no separate
`fp_bid` field. The `key` also accepts a version tag (`pb_live_xxx:v3`) to run a
specific playbook version, though we recommend deploying the version you want
from the dashboard rather than pinning it in code.

A KYB playbook that also verifies its beneficial owners can't run here: the
owners have to complete their own KYC, which this API can't prompt for. Run
those playbooks through an onboarding session instead.

### Step 4a: Optional configuration

| Attribute                  | Description                                                                                                                                                                                                                                                                                                       |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `external_id`              | Reference the user or business by your own identifier instead of `fp_id`. This is the same `external_id` set when the entity was created. Cannot be provided alongside `fp_id`.                                                                                                                                   |
| `onboarding_external_id`   | Use to control onboarding idempotency and to associate an onboarding with an event in your application (for example, an account application ID). If an onboarding with this ID already exists on the playbook, its result is reused and returned. If not, a new onboarding is created and the entity re-onboards. |
| `synchronous_timeout_secs` | Wait up to this many seconds (maximum 30) for the run to finish and return the decision inline. Omit to run asynchronously.                                                                                                                                                                                       |
| `prerequisite_data`        | If your playbook requires additional information from your backend, configure a prerequisite node on the playbook and pass that data here. Only accepted when the playbook has a prerequisite node.                                                                                                               |

By default, an entity may only onboard onto a playbook one time. This prevents you from incurring accidental charges for repeat onboardings. Running `POST /onboardings` again without a new `onboarding_external_id` returns a `409` with the error "Already onboarded onto playbook".

See [Run a playbook](/articles/integrate/onboardings) for the full reference on
`POST /onboardings`, including reonboarding patterns, KYB, and passing
onboarding data.

## Step 5: Handle the decision

Your playbook defines a set of rules used to evaluate the entity and to decide
whether it should be flagged for manual review. Use the `status` and
`requires_manual_review` fields in the response to decide whether or not to
onboard this user to your product.

| **STATUS**               | **WHAT DOES THIS MEAN?**                                                                                                                                                                                                                                                       |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `pass` / `fail` / `none` | An action node in your playbook executed and set the user's status. Conventionally, these are the output of rules that you define.                                                                                                                                             |
| `pending`                | Either you ran asynchronously, or the playbook took longer than `synchronous_timeout_secs` to execute. Certain steps of a playbook, like AI agents or verification checks, may take longer. The final decision will be delivered via [webhooks](/articles/integrate/webhooks). |
| `incomplete`             | The onboarding could not finish headlessly because more information is needed from the user. Use `continue_onboarding` to let them finish (see below).                                                                                                                         |
| `error`                  | The run failed due to logic configured on your playbook. See `error_message`.                                                                                                                                                                                                  |

| **REVIEW** | **WHAT DOES THIS MEAN?**                                                                                                                                                      |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| False      | Your playbook's rules have made a decision automatically.                                                                                                                     |
| True       | Your playbook's rules have requested that this user is reviewed manually before onboarding OR the user’s previous onboarding status caused the review flag to remain enabled. |

If Footprint still needs information from the user (for example, your playbook
collects a field you didn't vault, or requires an identity document), the status
is `incomplete` and the response carries a `continue_onboarding` object:

```json
{
  "id": "ob_SRFT2a1mN7DAWJ0VPXkiqK",
  "status": "incomplete",
  "requires_manual_review": false,
  "error_message": null,
  "continue_onboarding": {
    "token": "obtok_UxM6Vbvk2Rcy1gzcSuXgk3sj3L9I0pAnNH",
    "link": "https://verify.onefootprint.com/?type=user#obtok_UxM6Vbvk2Rcy1gzcSuXgk3sj3L9I0pAnNH",
    "expires_at": "2025-05-08T12:00-07:00"
  }
}
```

The token resumes this exact onboarding: the user picks up where the headless
run stopped, and the completed run keeps the same `id`. Treat the token as a
secret, and use it before `expires_at` (12 hours). Hand it to your frontend to
let the user finish the remaining steps, using either of the two launch options
below.

### Step 5a: Finish via Hosted

If using the **Hosted launch option**, extract the `link` and deliver it to your
end user via email, SMS, or through a button in your app.

### Step 5b: Finish via Embedded

If using one of our embedded SDKs, extract the `token` and use that to launch
Footprint. Below find some examples of different SDKs.

**Install:** Web (JS/TS/React/Vue/Angular): `npm install @onefootprint/footprint-js`. iOS: via
[Swift Package Manager](/articles/sdks/swift-introduction#installation-swift-package-manager)
or [Cocoa Pods](/articles/sdks/swift-introduction#installation-cocoa-pods). Android: see the
[installation instructions](/articles/sdks/android-introduction#installation).

```tsx filename="Web (React)"
import "@onefootprint/footprint-js/dist/footprint-js.css";
import { onboarding } from "@onefootprint/footprint-js";

const App = () => {
  const launch = () => {
    onboarding.initialize({
      // replace with `continue_onboarding.token` from the api response
      onboardingSessionToken: "obtok_UxM6Vbvk2Rcy1gzcSuXgk3sj3L9I0pAnNH",
      onComplete: (validationToken) => {
        console.log("completed", validationToken);
      },
    });
  };

  return <button onClick={launch}>Verify Identity</button>;
};
```

```swift filename="iOS (Swift UI)"
import SwiftUI
import Footprint

struct ContentView: View {
    var body: some View {
        VStack {
            Button("Verify Identity") {
                Task {
                    do {
		                    // replace with `continue_onboarding.token` from the api response
                        try await Footprint.shared.initialize(authToken: "obtok_UxM6Vbvk2Rcy1gzcSuXgk3sj3L9I0pAnNH")                       
                        
                        try await FootprintHosted.shared.launchHosted(
                            onComplete: { validationToken in
                                print("Handoff completed successfully with token: \(validationToken)")
                            },
                            onCancel: {
                                print("Handoff was canceled by the user")
                            },
                            onError: { error in
                                print("Error occurred during handoff: \(error)")
                            }                            
                        )
                    } catch {
                        print("Error: \(error)")
                    }
                }
            }
        }
    }
}
```

```kotlin filename="Android (Kotlin)"
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            Column {
                val coroutineScope = rememberCoroutineScope()
                Button(onClick = {
                    coroutineScope.launch {
                        try {
                            Footprint.initialize(
		                            // replace with `continue_onboarding.token` from the api response
                                authToken = "obtok_UxM6Vbvk2Rcy1gzcSuXgk3sj3L9I0pAnNH",
                                context = this@MainActivity
                            )

                            FootprintHosted.launchHosted(
                                context = this@MainActivity,
                                onComplete = { token: String ->
                                    println("VerificationResult: the flow has completed. The validation token is $token")
                                },
                                onCancel = {
                                    println("VerificationResult: the flow was canceled")
                                },
                                onError = { error ->
                                    println("Footprint error occurred: ${error}")
                                }
                            )
                        } catch (e: FootprintException) {
                            println("Error initializing Footprint SDK: ${e.message}")
                        }
                    }
                }) {
                    Text("Verify identity")
                }
            }
        }
    }
}
```

When the user finishes the remaining steps, the SDK invokes the `onComplete`
handler with a `validation_token`. To get the state of the onboarding
immediately, validate that token (step 5c). The final decision is also
delivered via webhook (step 6), and you can fetch it at any time with
GET
/users/{fp_id}/onboardings/{id} using the onboarding `id`
from step 4.

### Step 5c: Process the validation\_token

Note: this only applies if you are using the **Embedded** SDK. If using
**Hosted**, skip to step 6.

At the end of the onboarding flow, the SDK invokes an `onComplete` completion
handler that passes a `validation_token` to your code. Send the
`validation_token` to your backend and validate it using the
POST
/onboarding/session/validate API. This is the recommended way
to get the state of the onboarding immediately after the user finishes the flow.

```bash
curl -X POST https://api.onefootprint.com/onboarding/session/validate \
   -u <SECRET_API_KEY>: \
   -d '{"validation_token": "<VALIDATION_TOKEN>"}'
```

Note: This is a short-lived token that represents a completed onboarding. It
can be exchanged for the result of the onboarding (the decision in the case of
KYC/KYB) along with the `fp_id`/`fp_bid` of the corresponding entity that was
created or reused.

The response will look like:

```json
{
  "user": {
    "fp_id": "fp_id_GSxJr68GAf5jUT3pdL9ndjf7TLkA3GCX",
    "onboarding_id": "ob_SRFT2a1mN7DAWJ0VPXkiqK",
    "playbook_key": "pb_test_VMooXd04EUlnu3AvMYKjMW",
    "requires_manual_review": false,
    "status": "pass"
  },
  ...
}
```

## Step 6: Listen to webhooks

Every onboarding's decision is delivered via webhook when it reaches a terminal
status. For asynchronous runs or synchronous runs that returned `pending`,
webhooks are how you receive the final result.

### Step 6a: Subscribe a webhook endpoint

To enable webhook events, you need to register webhook endpoints in
the [Footprint dashboard](https://dashboard.onefootprint.com/webhooks). After
you register them, Footprint can push real-time event data to your application's
webhook endpoint when events happen in Footprint.

To get started, see
the [guide to consuming webhooks](https://docs.svix.com/receiving/introduction).

> We recommend that you secure your integration by always verifying that all
> webhook requests are generated by Footprint. Please see our guide on
> [securely verifying webhook signature](https://docs.svix.com/receiving/verifying-payloads/how)s.

### Step 6b: Recommended events

For the core Footprint integration, we recommend listening to the following
three event types.

* **Onboarding completed**

  We recommend subscribing to the
  footprint.onboarding.completed
  event, which will fire when an onboarding has reached a terminal status.

  In most cases, a synchronous `POST /onboardings` call will return a
  terminal `pass`, `fail`, or
  `none` [decision](#the-end-to-end-integration-step-5-handle-the-decision) inline.
  This webhook event delivers the final decision when you run an onboarding
  asynchronously or when identity verification vendors are taking longer to
  verify a user.

* **Manual review**

  Employees at your company may change a user's status while manually reviewing
  a user in the Footprint dashboard. This will fire the
  footprint.user.manual\_review
  event. Upon receiving this webhook, we recommend consulting the
  GET
  /users/{fp_id} API for information on the user's new manual
  status.

* **Watchlist checks**

  If your playbook has continuous monitoring enabled, Footprint will regularly
  check if any of your users are found on AML watchlists. Updates on watchlist
  checks are sent using the
  footprint.watchlist\_check.completed
  event.

# Appendix

## Advanced integration options

Below we’ve high-lighted some additional, more advanced options available to you
as you integrate Footprint into your product. All of these are optional and
using these methods typically indicate you are using Footprint in less common
ways. Note some of these APIs may be gated, so please reach out to us if an API
you need is not enabled on your account.

## Use the API to fetching Onboardings, PII, Decisions, Risk signals, Documents, and more

In more advanced integration cases, you may want to process more detailed
verification data and results from Footprint (instead of only viewing it in the
dashboard). This can be useful if you are embedding results from Footprint in
your application (i.e. a seller marketplace app who needs to pass risk signals
to the seller who will verify a buyer for large transactions).

### The Onboarding object

Every time a user or business onboards onto a playbook, an “onboarding” is
generated, and each onboarding has a unique ID often referenced as
`onboarding_id`. These IDs can be used to fetch risk signals, decisions,
documents and data collected, and more at a specific “onboarding”. An onboarding
ID lets you track the user as they update and modify data over time.

The two main places where onboarding IDs will be provided to you are:

1. The `id` field of the
   POST
   /onboardings response
2. A Webhook when an onboarding is completed.

You can list onboardings for a user/business by using the
GET
/users/{fp_id}/onboardings and
GET
/businesses/{fp_bid}/onboardings APIs.

### Fetch risk signals, and list collected data and documents by onboarding ID

Once you have a particular `onboarding_id` you can use the
GET
/users/{fp_id}/onboardings/{onboarding_id}/risk\_signals and
GET
GET /users/{fp_id}/documents?onboarding\_id=ob\_xyz.. APIs to
fetch onboarding specific risk signals and captured documents.

### Fetch decisions

Given a user or a business, you can fetch all the decisions (including manual
review decisions that occurred with a human in the loop). Use the
[list all decisions](/api-reference#get-users-fp-id-decisions) API (for
users/businesses). The response will look like following:

```json
{
  "data": [
    {
      "kind": "playbook_run",
      "playbook_key": "pb_live_fZvYlX3JpanlQ3MAwE45g0",
      "status": "fail",
      "timestamp": "2022-01-04T12:00-07:00"
    },
    {
      "kind": "manual",
      "status": "pass",
      "timestamp": "2022-01-04T12:00-07:00"
    }
  ],
  "meta": {
    "next_page": 2
  }
}
```

### Decrypt PII/documents from the vault at a specific onboarding

Footprint’s information systems are built around highly-secured vaulting
infrastructure with granular access controls. Use the
[decrypt API](/api-reference#post-users-fp-id-vault-decrypt) to access plaintext
sensitive user/business data.

```bash
curl -X POST https://api.onefootprint.com/users/{fp_id}/vault/decrypt \
  -u <API_KEY>: \
  -d '{
    "fields": [
      "id.ssn9",
      "id.last_name",
      "document.passport.front.image",
      "document.passport.dob"
    ],
    "reason": "compliance",
    "at_onboarding_id": "ob_id_xyz..."
  }'
```

You may specify the `at_onboarding_id` field to decrypt a user's historical
information at the time of the provided onboarding.

## Linking users to businesses via API

If you are using KYB playbooks but not verifying beneficial owners (i.e. you are
not using Footprint’s feature of verifying business owners together with the
business), you can still take advantage of linking users to their businesses
(and vice-versa) so that you can connect entities in the API and see the
connections in the Footprint dashboard. Use the
[link a business owner](/api-reference#post-businesses-fp-bid-owners) API after
onboarding both a business and the user(s).