# Quick Start: Your First Verification

This guide has two parts:

- [​Part 1](https://developer.incode.com/docs/quickstart#part-1-create-and-run-a-verification-session) runs a real verification session using a hosted Incode Webflow—no code required. You see exactly what your customers experience before you write a single line of integration code.
- [Part 2](https://developer.incode.com/docs/quickstart#part-2-integrate-the-web-sdk) walks you through a minimal Web SDK integration that produces the same result programmatically. By the end, your app creates a session, runs ID and selfie capture, and reads the verification score.

***

## Part 1: Create and Run a Verification Session

You will run a test verification session using a template Workflow.

### Step 1.1: Create a Workflow in Dashboard

A Workflow defines the verification steps your users will go through. You need one before you can run a session.

1. Log in to Dashboard and click **Workflows** in the left menu.
2. Click **New**.
3. In the lower left corner of the Workflow builder, click **Template**.
4. Locate the Identity Verification template and click **Use**. This template comes pre-configured with the standard modules for a complete verification: Data Sharing Consent, ID Capture, ID Validation, Face Capture, Face Match, and a result condition.
5. If prompted to replace what's in the canvas with the template, click **Replace with template**.
6. In the top left corner, click **Edit** to give your Workflow a meaningful name.
7. Click **Save & Publish**.

> 📘 Note
>
> You can configure Workflow-level settings and module-level settings to customize the data collected and the user experience. For this quick start, we recommend leaving the default configuration as-is.

For a deeper look at creating Workflows, using templates, and changing configuration settings, see [Workflows](https://developer.incode.com/docs/workflows-20)​.

### Step 1.2: Copy Your Workflow ID and Onboarding URL

When the Workflow is saved, Dashboard assigns it a unique Configuration ID (also called a Workflow ID). Active Workflows also have an Onboarding URL. You will need both later in this guide. To copy them:

1. In the left menu, click **Workflows**.
2. In the Actions column for the Workflow you created, click the three-dot menu.
3. Click **Copy ID**. This is the Workflow ID. Paste it somewhere you can easily access it later.
4. Click **Copy URL**. This is the Onboarding URL. Paste it somewhere you can easily access it later.

You can also generate a session URL on demand from your back end. You will do this in Part 2.

### Step 1.3: Complete a Test Session

1. In the Actions column for the Workflow you created, click **Test workflow**.
2. Scan the QR code on your phone to open the Onboarding.
3. Go through the verification steps:
   1. Grant camera permission when prompted.
   2. Scan the front and back of a government-issued ID.
   3. Take a selfie when prompted.
   4. Wait for the result screen.

The whole process should take under two minutes.

### Step 1.4: Review the Session in Dashboard

In Dashboard, click **Sessions**. Your test session appears. Click it to review the scores, extracted OCR data, captured images, and the overall verification result.

This is what your operations team will see for every user who goes through Onboarding.

> 📘 Note
>
> A session has three phases: capture (the customer), process (Incode), and result (your back-end decision). Part 2 shows how to drive that same flow from your own application code.

***

## Part 2: Integrate the Web SDK

You will build a minimal web application with two parts:

- A **back-end endpoint** that creates an Incode session and returns the session token to your front end.
- A **front-end page** that loads the Incode Web SDK, runs the verification modules, and calls your back end to retrieve the score when the session finishes.

### Step 2.1: Obtain Prerequisites

Before you start, make sure you have:

- Node.js 18 or higher, for the back-end server.
- A browser with camera access. Chrome is recommended. Use your phone for best capture quality.
- Your API key, API URL, and Flow configuration ID.
- The Configuration ID of the Workflow you created in Step 1.1. You can find it in the Workflow's detail view in Dashboard.

> 📘 Note
>
> The demo environment at `https://demo-api.incodesmile.com` is available for testing. Use it in place of your API URL until you are ready for production.

### Step 2.2: Set Up Your Project

1. Create a project folder and install dependencies:
   ```bash
   mkdir incode-quickstart && cd incode-quickstart
   npm init -y
   npm install express node-fetch dotenv
   ```

2) Create a `.env` file in the project root:
   ```bash
   API_URL=https://demo-api.incodesmile.com
   API_KEY=<YOUR_API_KEY>
   FLOW_ID=<YOUR_WORKFLOW_CONFIGURATION_ID> # from Step 1.1
   ```

> ⚠️ Warning
>
> Your API key must never appear in front-end code. This setup keeps it on the back end where it belongs.

### Step 2.3: Create the Back-End Server

1. Create `server.js`:
   ```javascript
   require('dotenv').config();
   const express = require('express');
   const app = express();

   app.use(express.json());
   app.use(express.static('public'));

   const API_URL = process.env.API_URL;
   const API_KEY  = process.env.API_KEY;
   const FLOW_ID  = process.env.FLOW_ID;

   // POST /start — create a new Incode session and return the token to the frontend
   app.post('/start', async (req, res) => {
     try {
       const response = await fetch(`${API_URL}/omni/start`, {
         method: 'POST',
         headers: {
           'Content-Type': 'application/json',
           'api-version': '1.0',
           'x-api-key': API_KEY,
         },
         body: JSON.stringify({
           countryCode: 'ALL',
           configurationId: FLOW_ID,
           // externalCustomerId: req.body.userId, // optional: link to your user record
         }),
       });

       if (!response.ok) {
         throw new Error(`Incode API error: ${response.status}`);
       }

       // Return token and interviewId to the frontend.
       // The frontend only ever sees the token — never the API key.
       const { token, interviewId } = await response.json();
       res.json({ token, interviewId });
     } catch (err) {
       console.error('Session creation failed:', err.message);
       res.status(500).json({ error: 'Could not create session' });
     }
   });

   // POST /finish — mark the session complete and fetch the score
   app.post('/finish', async (req, res) => {
     const { token, interviewId } = req.body;

     try {
       // Fetch the verification score using the session token.
       // X-Incode-Hardware-Id carries the session token for /0/ endpoints.
       const scoreRes = await fetch(`${API_URL}/0/omni/get/score`, {
         method: 'GET',
         headers: {
           'api-version': '1.0',
           'x-api-key': API_KEY,
           'X-Incode-Hardware-Id': token,
         },
       });

       if (!scoreRes.ok) {
         throw new Error(`Score fetch failed: ${scoreRes.status}`);
       }

       const score = await scoreRes.json();

       // In production, apply your business rules here.
       // score.idValidation.overall.status  → 'OK', 'WARN', 'FAIL'
       // score.liveness.overall.status      → 'OK', 'WARN', 'FAIL'
       // score.faceRecognition.overall.status → 'OK', 'WARN', 'FAIL'
       // score.overall.status               → 'OK', 'WARN', 'FAIL', 'MANUAL'

       res.json({ interviewId, score });
     } catch (err) {
       console.error('Finish failed:', err.message);
       res.status(500).json({ error: 'Could not retrieve score' });
     }
   });

   app.listen(3000, () => console.log('Server running at http://localhost:3000'));
   ```
2. Start the server:
   ```bash
   node server.js
   ```

### Step 2.4: Create the Front-End Page

Create a `public/` folder, then create `public/index.html`:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Incode Quick Start</title>
  <!-- Load the Incode Web SDK from CDN -->
  <script src="https://sdk.incode.com/sdk/onBoarding-1.85.0.js" defer></script>
  <style>
    body { font-family: sans-serif; max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
    #camera-container { width: 100%; min-height: 300px; }
    #result { margin-top: 1.5rem; padding: 1rem; background: #f5f5f5; border-radius: 6px; display: none; }
    pre { font-size: 13px; overflow: auto; }
  </style>
</head>
<body>
  <h1>Identity Verification</h1>
  <p id="status">Starting session...</p>
  <div id="camera-container"></div>
  <div id="result">
    <h2>Verification Complete</h2>
    <pre id="score-output"></pre>
  </div>

  <script>
    const container   = document.getElementById('camera-container');
    const statusEl    = document.getElementById('status');
    const resultEl    = document.getElementById('result');
    const scoreOutput = document.getElementById('score-output');

    let incode  = null;
    let session = null; // { token, interviewId }

    async function init() {
      // Step 1: Ask the backend to create a session. 
      // The API key stays on the server — the frontend only receives the token.
      const res  = await fetch('/start', { method: 'POST' });
      session    = await res.json();

      // Step 2: Initialize the Web SDK with your API URL and the session token.
      // Do NOT pass the API key here.
      incode = window.OnBoarding.create({
        apiURL: 'https://demo-api.incodesmile.com/0', // note the /0 path
        token:  session.token,
      });

      statusEl.textContent = 'Session ready. Starting ID capture...';
      captureIdFront();
    }

    // Step 3: Capture the front of the ID document
    function captureIdFront() {
      incode.renderCamera('front', container, {
        token:         session,
        numberOfTries: 3,
        onSuccess:     captureIdBack,
        onError:       handleError,
      });
    }

    // Step 4: Capture the back of the ID document (skip for passports)
    function captureIdBack() {
      incode.renderCamera('back', container, {
        token:         session,
        numberOfTries: 3,
        onSuccess:     processId,
        onError:       handleError,
      });
    }

    // Step 5: Tell Incode to process the captured ID images
    async function processId() {
      statusEl.textContent = 'Processing ID...';
      container.innerHTML  = '<p>Processing your ID — please wait.</p>';
      await incode.processId({ token: session.token });
      captureSelfie();
    }

    // Step 6: Capture the selfie
    function captureSelfie() {
      statusEl.textContent = 'ID processed. Starting selfie capture...';
      incode.renderCamera('selfie', container, {
        token:         session,
        numberOfTries: 3,
        showTutorial:  true,
        onSuccess:     finishSession,
        onError:       handleError,
      });
    }

    // Step 7: Send the session token to your backend to mark completion and fetch the score
    async function finishSession() {
      statusEl.textContent = 'Verification complete. Fetching results...';
      container.innerHTML  = '';

      const res    = await fetch('/finish', {
        method:  'POST',
        headers: { 'Content-Type': 'application/json' },
        body:    JSON.stringify(session),
      });
      const result = await res.json();

      // Display the score for this tutorial — in production, apply your business rules
      resultEl.style.display    = 'block';
      scoreOutput.textContent   = JSON.stringify(result.score, null, 2);
      statusEl.textContent      = `Session ${result.interviewId} complete.`;
    }

    function handleError(err) {
      statusEl.textContent = `Error: ${err?.message || 'An error occurred'}`;
      console.error(err);
    }

    // Run on page load
    window.addEventListener('DOMContentLoaded', init);
  </script>
</body>
</html>
```

> 📘 Note
>
> The CDN URL above references SDK version `1.85.0`. Always use the latest version — check [Web SDK Release Notes](https://developer.incode.com/docs/releases-web-sdk) for the current version number.

***

### Step 2.5: Run It on Your Phone

The Incode Web SDK requires HTTPS and a real camera. For local development, use a tunneling tool to expose your local server over HTTPS:

```bash
# Using ngrok (install from ngrok.com if needed)
ngrok http 3000
```

Copy the `https://` URL ngrok provides and open it on your phone. Go through the verification as you did in Part 1.

When the selfie capture completes, your back end fetches the score and the result appears on screen.

***

### Step 2.6: Read the Score

Your `/finish` endpoint returns a score object that looks like this:

```json
{
  "idValidation": {
    "overall": {
      "value": "98.0",
      "status": "OK"
    }
  },
  "liveness": {
    "overall": {
      "value": "100.0",
      "status": "OK"
    }
  },
  "faceRecognition": {
    "overall": {
      "value": "97.5",
      "status": "OK"
    }
  },
  "overall": {
    "status": "OK"
  }
}
```

Use the `status` field—not the numeric `value`—to make decisions. The possible status values are:

| Status    | Meaning                                                 |
| --------- | ------------------------------------------------------- |
| `OK`      | Module passed.                                          |
| `WARN`    | Score is borderline; consider routing to manual review. |
| `MANUAL`  | Session requires a human reviewer.                      |
| `FAIL`    | Module failed.                                          |
| `UNKNOWN` | Result could not be determined.                         |

A typical decision pattern in your back end:

```javascript
const { overall } = score;

if (overall.status === 'OK') {
  // Approve the user and continue your onboarding flow
} else if (overall.status === 'WARN' || overall.status === 'MANUAL') {
  // Route to manual review queue
} else {
  // Reject and prompt the user to try again or contact support
}
```

***

## What You Built

In Part 1 you ran a live verification session and saw the result in Dashboard. In Part 2 you built a working integration that:

1. Creates a session from your back end using your API key.
2. Passes only the session token to the front end.
3. Runs ID capture, ID processing, and selfie capture using the Web SDK.
4. Retrieves the verification score from your back end after the session completes.

This is the foundation every Incode web integration is built on. The same pattern applies whether you add more modules, switch to webhooks for async results, or move to a mobile SDK.

***

## What's Next

| Goal                                              | Where to go                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Add consent, geolocation, or eKYC modules         | [Web SDK Tutorial (Full)](https://developer.incode.com/docs/web-integrations-websdk)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| Build a React integration                         | [Extended Onboarding: React](https://developer.incode.com/docs/onboarding-extended-react)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| Receive results asynchronously instead of polling | [Webhooks](https://developer.incode.com/docs/webhooks-overview)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| Use a mobile SDK instead                          | [iOS](https://developer.incode.com/docs/mobile-integrations-ios) · [Android](https://developer.incode.com/docs/mobile-integrations-android) · [React Native](https://developer.incode.com/docs/mobile-integrations-react-native) · [Flutter](https://developer.incode.com/docs/mobile-integrations-flutter) · [Xamarin](https://developer.incode.com/docs/mobile-integrations-xamarin) · [Cordova](https://developer.incode.com/docs/mobile-integrations-cordova) · [Ionic Capacitor](https://developer.incode.com/docs/mobile-integrations-ionic-capacitor) |
| Understand all score fields                       | [Fetch Score Data](fetch-scores)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |

<br />
