# Lever + Incode Candidate Biometric Identity Verification

**Integration Guide · Tines Automation · v1.0**

Automate biometric identity verification for candidates in your Lever ATS pipeline. When a candidate advances to a designated hiring stage, this Tines automation sends a personalized Incode verification link, captures the result, and writes it back to the candidate's Lever opportunity — with no manual steps required.

---

## Table of Contents

01. [Overview](https://workforce.developer.incode.com/docs/lever-integration#overview)  
02. [How It Works](https://workforce.developer.incode.com/docs/lever-integration#how-it-works)  
03. [Prerequisites](https://workforce.developer.incode.com/docs/lever-integration#prerequisites)  
04. [Step 1 — Configure Credentials](https://workforce.developer.incode.com/docs/lever-integration#step-1--configure-credentials)  
05. [Step 2 — Set Up the Lever Webhook](https://workforce.developer.incode.com/docs/lever-integration#step-2--set-up-the-lever-webhook)  
06. [Step 3 — Set Up the Incode Webhook](https://workforce.developer.incode.com/docs/lever-integration#step-3--set-up-the-incode-webhook)  
07. [Step 4 — Configure Your Lever Stage ID](https://workforce.developer.incode.com/docs/lever-integration#step-4--configure-your-lever-stage-id)  
08. [Step 5 — Import the Tines Story](https://workforce.developer.incode.com/docs/lever-integration#step-5--import-the-tines-story)  
09. [Automation Agents Reference](https://workforce.developer.incode.com/docs/lever-integration#automation-agents-reference)  
10. [API Reference](https://workforce.developer.incode.com/docs/lever-integration#api-reference)  
11. [Email Template](https://workforce.developer.incode.com/docs/lever-integration#email-template)  
12. [Tines Records Schema](https://workforce.developer.incode.com/docs/lever-integration#tines-records-schema)  
13. [Troubleshooting](https://workforce.developer.incode.com/docs/lever-integration#troubleshooting)

---

## Overview

This integration uses a **Tines automation story** as the orchestration layer between Lever and Incode Workforce. It handles the full candidate verification lifecycle:

- Validates incoming Lever webhook signatures via HMAC-SHA256
- Triggers only when a candidate reaches your designated verification stage
- Fetches candidate details from Lever, generates an Incode verification link, and emails it to the candidate
- Stores a trace record in Tines to map verification results back to the correct Lever opportunity
- Listens for Incode verification status updates and writes results (notes + tags) back to Lever automatically

> **Note:** This guide covers the Tines-based integration story. You will need an active Tines workspace, a Lever account with API access, and an Incode Workforce account with API credentials to proceed.

---

## How It Works

The automation runs as two independent flows that communicate via a shared Tines Records table.

### Flow 1 — Trigger & Send Verification

Fires when a candidate moves to the verification stage in Lever.

```undefined
[Stage change webhook] → [Validate signature] → [Check stage ID]
        ↓
[GET /candidates/{id}] → [Extract name & email] → [3 min delay]
        ↓
[GET Incode auth token] → [Generate verification link]
        ↓
[Capture Tines record] → [Send email to candidate]
```

### Flow 2 — Receive Result & Update Lever

Fires when Incode posts a verification status event to your Tines webhook.

```undefined
[Status update webhook] → [Check event_type == succeeded]
        ↓
[Match trace ID → OpportunityID]
        ↓
[Add tag: biometric-verified] → [Post verification note]
```

> **Tip:** Flow 2 is triggered only on `verification.succeeded` events. Failed or expired verifications are written as notes to Lever but do not trigger the tag update. You can extend the story to handle additional event types such as `verification.failed` for custom workflows.

---

## Prerequisites

| Requirement | Details |
| --- | --- |
| **Tines** | An active workspace with permission to create stories, credentials, and records. The Records feature must be enabled. |
| **Lever ATS** | An account with Admin or Super Admin access to create API keys and configure webhooks. |
| **Incode Workforce** | An active integration with integration ID, secret, and x-api-key available. |
| **Email sending** | Tines email delivery is used to send verification links. Ensure your workspace domain is whitelisted for outbound email. |

---

## Step 1 — Configure Credentials

This story requires five credentials stored in your Tines workspace. Navigate to **Credentials** in the Tines sidebar and create each one as a _Text_ credential.

| Credential Name | Source | Description |
| --- | --- | --- |
| `lever_api_key` | Lever → Settings → Integrations → API Credentials | Your Lever API key. Used as a Bearer token to fetch candidate data, post notes, and add tags. |
| `incode_x_api_key` | Incode Dashboard → API Keys | The `x-api-key` header value for Incode's server-side authorization endpoint. |
| `incode_workforce_integrationid` | Incode Dashboard → Workforce Integration | The unique identifier for your Incode Workforce integration instance. |
| `incode_workforce_secret` | Incode Dashboard → Workforce Integration | The secret associated with your Workforce integration, used for server-side token generation. |
| `tines_api_key_lever_incode_records` | Tines → Your Profile → API Keys | A Tines API key used by the story to query its own Records table when matching a trace ID to an opportunity. |

> **Warning:** Credential names must match exactly as shown above. The story references them using `CREDENTIAL.NAME` syntax — a typo will cause silent authentication failures. The webhook signature secret for Lever is hardcoded in the story and will need to be updated to match your own Lever webhook signing secret (see Step 2).

---

## Step 2 — Set Up the Lever Webhook

Lever must be configured to send a webhook event whenever a candidate's pipeline stage changes.

**1. Copy the Tines webhook URL**

After importing the story (Step 5), open the `lever_webhook` agent in your Tines story. Copy the generated webhook URL:

```undefined
https://<your-tenant>.tines.com/webhook/<story-guid>/871aa415a5da64cdfa3fba63527c374b
```

**2. Create the webhook in Lever**

In Lever, go to **Settings → Integrations → Webhooks** and add a new webhook with:

| Field | Value |
| --- | --- |
| URL | Your Tines webhook URL from above |
| Events | `candidateStageChange` |
| Signing token | Copy this value — you'll need it in the next step |

**3. Update the HMAC signature secret in the story**

Open the `validate_webhook_signature` agent in Tines. Find the HMAC expression and replace the hardcoded signing token with your actual Lever webhook signing token:

JSON

```json
// In validate_webhook_signature agent payload:
{
  "calculated_signature_raw": "<<HMAC_SHA256(LOCAL.concatened, \"YOUR_LEVER_SIGNING_TOKEN_HERE\")>>"
}
```

> **Warning:** The story ships with a placeholder signing token. You must replace this with your own Lever signing token or the signature check will reject all incoming webhooks.

---

## Step 3 — Set Up the Incode Webhook

Incode must be configured to POST verification status events to a second Tines webhook endpoint. This drives Flow 2.

**1. Copy the verification status webhook URL**

After importing the story, open the `Get_verification_status_updates` agent. Copy its webhook URL:

```undefined
https://<your-tenant>.tines.com/webhook/<story-guid>/59ab814228451619d85618e952e62c34
```

**2. Register the webhook in Incode**

In the Incode Dashboard, navigate to your Workforce integration settings and configure a webhook pointing to the URL above. The story expects the following fields in the webhook payload:

JSON

```json
{
  "event_type": "verification.succeeded",
  "data": {
    "verification_trace_id": "<trace-id>",
    "failure_reason": "<status message>",
    "ip": "<candidate IP>",
    "user_name": "<candidate name>",
    "latitude": 0.0,
    "longitude": 0.0
  }
}
```

---

## Step 4 — Configure Your Lever Stage ID

The story triggers the verification flow only when a candidate advances to a specific Lever pipeline stage. You must update the stage ID to match a stage in your own Lever environment.

**1. Find the Stage ID in Lever**

Use the Lever API to list your pipeline stages:

Bash

```shell
curl -X GET https://api.lever.co/v1/stages \
  -H "Authorization: Bearer <your-lever-api-key>"
```

Each stage in the response has an `id` field. Copy the ID for your desired stage (e.g., "Background Check" or "Offer").

**2. Update the `validate_stage` agent**

Open the `validate_stage` agent in Tines and replace the stage ID in the trigger rule:

JSON

```json
// In validate_stage agent options.rules[0]:
{
  "type": "field==value",
  "value": "YOUR-STAGE-UUID-HERE",
  "path": "EXTRACT_DATA.TO_STAGE_ID"
}
```

> **Note:** The story ships with a sample stage ID (`73f73269-c81e-465f-bdf4-25c76929d60a`) which will not match your environment. Failing to update this means the story will receive all stage change events but never proceed past the `validate_stage` trigger.

---

## Step 5 — Import the Tines Story

**1. Download the story file**

Download `lever-incode-candidate-biometric-identity-verification.json` from your account manager.

**2. Import into Tines**

In your Tines workspace, click **New Story → Import** and upload the JSON file. Tines will create all agents, connections, and the Tines Records table automatically.

**3. Complete configuration**

Before enabling the story, confirm the following:

- [ ]  All five credentials are created in Tines  
- [ ]  Lever webhook is configured to POST to `lever_webhook`  
- [ ]  Lever HMAC signing token is updated in `validate_webhook_signature`  
- [ ]  Stage ID is updated in `validate_stage`  
- [ ]  Incode webhook is configured to POST to `Get_verification_status_updates`  
- [ ]  Email sender name and reply-to address updated in `Send Email Action`  
- [ ]  Redirect URL in `incode_verification` updated to your own domain

**4. Enable and test**

Enable the story in Tines. Move a test candidate to your verification stage in Lever and confirm the story fires, the email is sent, and a record is created in the Tines Records table.

---

## Automation Agents Reference

The story contains 16 agents across both flows.

### Flow 1 — Send Verification

| Agent | Type | Description |
| --- | --- | --- |
| `lever_webhook` | WebhookAgent | Receives all `candidateStageChange` POST events from Lever. Responds with HTTP 200 immediately. |
| `validate_webhook_signature` | EventTransformationAgent | Computes HMAC-SHA256 of `token + triggeredAt` using your Lever signing secret and compares it to the received signature. |
| `check_signature` | TriggerAgent | Gate: only passes events where the computed signature matches the received signature. Rejects forged or replayed webhooks. |
| `extract_data` | EventTransformationAgent | Extracts `candidateId`, `opportunityId`, `fromStageId`, `toStageId`, and `stageName` from the webhook payload. |
| `validate_stage` | TriggerAgent | Gate: only passes events where `toStageId` equals your configured verification stage ID. |
| `get_candidate_details` | HTTPRequestAgent | Calls `GET /v1/candidates/{id}` on the Lever API to retrieve the candidate's profile. |
| `extract_candidate_details` | EventTransformationAgent | Parses name, email, and phone from the Lever response. Splits the full name into `first_name` and `last_name` for Incode. |
| `Delay sending identity verification message` | EventTransformationAgent (delay) | Waits 3 minutes (180 seconds) before continuing. Allows stage transitions to settle in Lever before sending the email. |
| `get_incode_token` | HTTPRequestAgent | Calls the Incode server-side authorization endpoint to obtain a short-lived session token using your integration credentials. |
| `incode_verification` | HTTPRequestAgent | Generates a personalized, time-limited verification link for the candidate. Link is valid for 72 hours (4320 minutes). |
| `Capture Record` | RecordAgent | Creates a record in the `Lever_Incode_Information` table storing the opportunity ID, candidate ID, and Incode trace ID. |
| `Send Email Action` | EmailAgent | Sends an HTML-formatted email to the candidate containing the verification link and step-by-step instructions. |

### Flow 2 — Receive Result

| Agent | Type | Description |
| --- | --- | --- |
| `Get_verification_status_updates` | WebhookAgent | Receives POST callbacks from Incode containing verification results. Second entry point of the story. |
| `Trigger Action` | TriggerAgent | Gate: only passes events where `event_type == "verification.succeeded"`. |
| `Match_record_trace_opportunity` | HTTPRequestAgent | Queries the Tines Records API to find the opportunity ID matching the `verification_trace_id` from the Incode callback. |
| `update_lever_tag` | HTTPRequestAgent | Calls `POST /v1/opportunities/{id}/addTags` to add the `biometric-verified` tag to the candidate's Lever opportunity. |
| `update_lever_note` | HTTPRequestAgent | Posts a structured note to the Lever opportunity containing the event type, status, trace ID, IP, name, and geolocation from the Incode verification result. |

---

## API Reference

### Incode API

#### Get auth token

```undefined
POST https://demo-api-incode-id.incodesmile.com/v1/integration/authorize/server
```

**Headers:**

```undefined
x-api-key: CREDENTIAL.INCODE_X_API_KEY
```

**Request body:**

JSON

```json
{
  "integrationId": "CREDENTIAL.INCODE_WORKFORCE_INTEGRATIONID",
  "secret": "CREDENTIAL.INCODE_WORKFORCE_SECRET"
}
```

**Response:**

JSON

```json
{ "token": "<session-token>" }
```

The `token` value is passed as the `x-auth-token` header in all subsequent Incode API calls.

---

#### Generate verification link

```undefined
POST https://demo-api-incode-id.incodesmile.com/v1/workforce/verification/candidate/generate-verification-link
```

**Headers:**

```undefined
x-auth-token: GET_INCODE_TOKEN.BODY.TOKEN
```

**Request body:**

JSON

```json
{
  "integrationID": "CREDENTIAL.INCODE_WORKFORCE_INTEGRATIONID",
  "secret": "CREDENTIAL.INCODE_WORKFORCE_SECRET",
  "loginHint": "candidate@email.com",
  "loginHintType": "EMAIL",
  "validityMinutes": 4320,
  "redirectUrl": "https://your-domain.com",
  "givenNames": "First",
  "lastName": "Last"
}
```

**Key response fields:**

JSON

```json
{
  "verificationLink": "https://...",      // sent to candidate via email
  "verificationTraceId": "..."           // stored in Tines Records
}
```

---

### Lever API

#### Get candidate details

```undefined
GET https://api.lever.co/v1/candidates/{candidateId}
```

**Headers:**

```undefined
Authorization: Bearer CREDENTIAL.LEVER_API_KEY
```

**Key response fields used:**

```undefined
data.name         // full name — split into first/last
data.emails[0]    // primary email address
data.phones[0]    // primary phone number
```

---

#### Add tag to opportunity

```undefined
POST https://api.lever.co/v1/opportunities/{opportunityId}/addTags
```

**Headers:**

```undefined
Authorization: Bearer CREDENTIAL.LEVER_API_KEY
```

**Request body:**

JSON

```json
{ "tags": ["biometric-verified"] }
```

---

#### Post verification note

```undefined
POST https://api.lever.co/v1/opportunities/{opportunityId}/notes
```

**Headers:**

```undefined
Authorization: Bearer CREDENTIAL.LEVER_API_KEY
```

**Request body:**

JSON

```json
{
  "secret": false,
  "value": "Biometric Verification Update\n\nEvent: <event_type>\n\nVerification Details:\n- Status: ...\n- Trace ID: ...\n- IP: ...\n- Name: ...\n- Location: lat, lon\n\nProcessed: <timestamp>"
}
```

---

## Email Template

The `Send Email Action` agent delivers an HTML email to the candidate. The template includes a personalized greeting, a CTA button with the verification link, and step-by-step instructions for what candidates should expect during verification.

To customize the template, open the `Send Email Action` agent in Tines and update the following:

| Field | Default | What to Change |
| --- | --- | --- |
| `sender_name` | `GoodLeap Talent Team` | Your company's talent team name |
| `reply_to` | `t3@goodleap.com` | Your recruitment team's reply-to email address |
| `subject` | `Identity Verification Request` | Optionally add your company name |
| CTA button color | `#00AEEF` | Your brand's primary color |
| Contact email in body | `T3@goodleap.com` | Your recruitment team's email address |
| `redirectUrl` in `incode_verification` | `https://goodleap.com` | Your company website or careers page URL |

---

## Tines Records Schema

The story automatically creates and queries a Records table called `Lever_Incode_Information`. This table is the bridge between Flow 1 (send verification) and Flow 2 (receive result).

| Field Name | Type | Description |
| --- | --- | --- |
| `Story name` | TEXT | Name of the Tines story that created the record. For debugging. |
| `Timestamp` | TIMESTAMP | Date/time the record was created (verification request sent). |
| `OpportunityID` | TEXT | The Lever opportunity ID. Used when writing back notes and tags in Flow 2. |
| `CandidateID` | TEXT | The Lever candidate ID. Stored for reference and audit. |
| `TraceID` | TEXT | The Incode `verificationTraceId`. Used as the lookup key in Flow 2 to match the callback to the original opportunity. |
| `Updated at` | TIMESTAMP | Auto-populated timestamp for when the record was last modified. |

> **Note:** The `Match_record_trace_opportunity` agent queries this table using the Tines Records API at `https://<your-tenant>.tines.com/api/v1/records`, filtering by `TraceID` to find the corresponding Lever `OpportunityID`. Ensure `tines_api_key_lever_incode_records` has permission to read this table.

---

## Troubleshooting

### Story fires but no email is sent
Check the following in order:

- **`check_signature` trigger not passing** — verify your HMAC signing token in `validate_webhook_signature` matches the signing token shown in Lever's webhook settings exactly.
- **`validate_stage` trigger not passing** — verify the stage ID in the trigger rule matches the UUID of your intended pipeline stage in Lever.
- **`get_incode_token` returning non-200** — check your `incode_x_api_key`, `incode_workforce_integrationid`, and `incode_workforce_secret` credentials in Tines.

### Lever note/tag not written after verification
- Confirm Incode's webhook is correctly configured to POST to the `Get_verification_status_updates` URL.
- Check that the `event_type` from Incode is exactly `verification.succeeded` — the `Trigger Action` gate performs a strict string match.
- Confirm a Tines record exists for the `verification_trace_id` — check the Records table to verify Flow 1 ran successfully for that candidate.
- Confirm `tines_api_key_lever_incode_records` is valid and has read access to the `Lever_Incode_Information` records table.

### Signature validation fails for all events
The HMAC signing token in `validate_webhook_signature` must match your Lever webhook's signing token exactly. Go to Lever → Settings → Integrations → Webhooks, click your webhook, and copy the signing token. Paste it into the `HMAC_SHA256` expression in the Tines agent, replacing any placeholder value.

### Incode API returns 401
Your `incode_x_api_key` or `incode_workforce_integrationid` / `incode_workforce_secret` credentials are incorrect or expired. Verify them in the Incode Dashboard and re-create the Tines credentials.

> **Tip:** Use Tines' built-in event log for each agent to inspect the exact payload received and response returned. This is the fastest way to identify where in the flow an issue occurs.
