# Lever

This page covers integrating Incode identity verification into the Lever Applicant Tracking System (ATS). This integration uses Tines, a powerful automation platform, to orchestrate the verification flow between Lever and Incode. When a candidate reaches a designated hiring stage, Tines sends them a personalized verification link, captures the result, and writes it back to their Lever opportunity automatically.

This integration handles the full candidate verification lifecycle. It:

- Validates incoming Lever webhook signatures using 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, including notes and tags, back to Lever automatically.

> 📘 **Note**
>
> You will need an active Tines workspace, a Lever account with API access, and an Incode account with API credentials to proceed.

***

## Prerequisites

Ensure you have the following before you begin:

| 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        | 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 allowlisted for outbound email. |

***

## Understand Flows

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

### Flow 1: Trigger and Send Verification

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

```
[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 and Update Lever

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

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

> ⚠️ **Warning**
>
> Flow 2 triggers 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`.

***

## Set Up the Lever Integration

### Configure Credentials

This Tines story requires five credentials stored in your Tines workspace. Go 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`                   | Dashboard > API Keys                              | The `x-api-key` header value for Incode's authorization endpoint.                                            |
| `incode_workforce_integrationid`     | Dashboard > Integrations                          | The unique identifier for your Incode integration.                                                           |
| `incode_workforce_secret`            | Dashboard > Integrations                          | The secret for your 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. The story references them using `<<CREDENTIAL.name>>` syntax. A typo will cause silent authentication failures.

### Set Up the Lever Webhook

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

1. **Copy the Tines webhook URL**:**&#x20;**&#x41;fter importing the story, open the `lever_webhook` agent and copy the generated webhook URL:
   ```
   https://<your-tenant>.tines.com/webhook/<story-guid>/871aa415a5da64cdfa3fba63527c374b
   ```
2. **Create the webhook in Lever**: In Lever, go to **Settings&#x20;**>**&#x20;Integrations** >**&#x20;Webhooks** and add a new webhook:
   | 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 and replace the placeholder signing token with your Lever webhook signing token:
   ```json
   {
     "calculated_signature_raw": "<<HMAC_SHA256(LOCAL.concatened, \"YOUR_LEVER_SIGNING_TOKEN_HERE\")>>"
   }
   ```

> 🚧 **Warning**
>
> The story ships with a placeholder signing token. Replace it with your Lever signing token or the signature check will reject all incoming webhooks.

### Set Up the Incode Webhook

Incode must 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 and copy its webhook URL:
   ```
   https://<your-tenant>.tines.com/webhook/<story-guid>/59ab814228451619d85618e952e62c34
   ```
2. **Register the webhook in Incode**: In Dashboard, go to **Configuration > Webhooks** and configure a webhook pointing to the URL above. The story expects the following fields in the webhook payload:
   ```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
     }
   }
   ```

### Configure Your Lever Stage ID

The story triggers 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
   curl -X GET https://api.lever.co/v1/stages \
     -H "Authorization: Bearer <your-lever-api-key>"
   ```
   Each stage has an `id` field. Copy the ID for your target stage: for example, Background Check or Offer.
2. **Update the&#x20;**`validate_stage`**&#x20;agent**: Open the `validate_stage` agent in Tines and replace the stage ID in the trigger rule:
   ```json
   {
     "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`) that does not match your environment. If you don't update it, the story receives all stage change events but never proceeds past the `validate_stage` trigger.

### Import the Tines Story

1. **Download the story file**: Download `lever-incode-candidate-biometric-identity-verification.json` from your Incode account manager.
2. **Import into Tines**: In your Tines workspace, click **New Story > Import** and upload the JSON file. Tines then creates 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 is updated in the Send Email Action.
   - Redirect URL in `incode_verification` is updated to your 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 appears 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 and triggeredAt using your Lever signing secret and compares it to the received signature.      |
| `check_signature`                             | TriggerAgent                     | Gate: passes only 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: passes only 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 full name into `first_name` and `last_name` for Incode.         |
| `Delay sending identity verification message` | EventTransformationAgent (delay) | Waits 3 minutes 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. The link is valid for 72 hours.                   |
| `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 with the verification link and 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: passes only 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 with the event type, status, trace ID, IP, name, and geolocation from the Incode verification result. |

***

## API Reference

### Incode APIs

**Get Auth Token**

```
POST https://demo-api-incode-id.incodesmile.com/v1/integration/authorize/server
x-api-key: <<CREDENTIAL.incode_x_api_key>>
```

Request body:

```json
{
  "integrationId": "<<CREDENTIAL.incode_workforce_integrationid>>",
  "secret": "<<CREDENTIAL.incode_workforce_secret>>"
}
```

Response:

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

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

**Generate Verification Link**

```
POST https://demo-api-incodesmile.com/v1/workforce/verification/candidate/generate-verification-link
x-auth-token: <<get_incode_token.body.token>>
```

Request body:

```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
{
  "verificationLink": "https://...",
  "verificationTraceId": "..."
}
```

### Lever APIs

**Get Candidate Details**

```
GET https://api.lever.co/v1/candidates/{candidateId}
Authorization: Bearer <<CREDENTIAL.lever_api_key>>
```

Key response fields used:

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

**Add Tag to Opportunity**

```
POST https://api.lever.co/v1/opportunities/{opportunityId}/addTags
Authorization: Bearer <<CREDENTIAL.lever_api_key>>
```

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

**Post Verification Note**

```
POST https://api.lever.co/v1/opportunities/{opportunityId}/notes
Authorization: Bearer <<CREDENTIAL.lever_api_key>>
```

```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 sends an HTML email to the candidate with a personalized greeting, a verification button, and step-by-step instructions.

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

| Field                                  | Default                       | Description                                    |
| -------------------------------------- | ----------------------------- | ---------------------------------------------- |
| `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 | Add your company name if needed                |
| 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 creates and queries a Records table called `Lever_Incode_Information`. This table connects Flow 1 (send verification) and Flow 2 (receive result).

| Field         | Type      | Description                                                                                                           |
| ------------- | --------- | --------------------------------------------------------------------------------------------------------------------- |
| Story name    | TEXT      | Name of the Tines story that created the record. For debugging.                                                       |
| Timestamp     | TIMESTAMP | Date and time the record was created.                                                                                 |
| 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`**&#x20;trigger not passing**: Verify the stage ID in the trigger rule matches the UUID of your target 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 or Tag Not Written After Verification**

Confirm the following:

- The Incode webhook is correctly configured to POST to the `Get_verification_status_updates` URL.
- The `event_type` from Incode is exactly `verification.succeeded`: the Trigger Action performs a strict string match.
- A Tines record exists for the `verification_trace_id`. Check the Records table to confirm Flow 1 ran successfully for that candidate.
- `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.&#x20;

1. Go to **Lever > Settings > Integrations > Webhooks**.
2. Click your webhook.
3. Copy the signing token.
4. Paste the signing token into the `HMAC_SHA256` expression in the Tines agent, replacing any placeholder value.

### **Incode API Returns 401**

Your `incode_x_api_key`, `incode_workforce_integrationid`, or `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. This is the fastest way to identify where an issue occurs.

<br />
