# Lists
Source: https://docs.verisoul.ai/action/lists
A comprehensive guide to managing user groups with Verisoul Lists
# Lists in Verisoul
Lists are a fundamental tool for organizing accounts and implementing specific fraud prevention workflows. They allow you to categorize users and apply different treatments based on their status.
For detailed API documentation on implementing Lists, see the [Lists API Reference](/api-reference/lists/get).
## Types of Lists
### Standard Lists
Verisoul provides three built-in lists that form the foundation of fraud prevention:
| List Name | Purpose | Effect |
| - | - | - |
| **Allow List** | Trusted accounts | Marks accounts as Real (affects decision field only) |
| **Block List** | Risky accounts | Marks accounts as Fake (affects decision field only) |
| **Main Account List** | Primary user accounts | Sets multiple\_accounts score to 0 |
### Custom Lists
Create your own lists for specific use cases such as:
* High-risk users requiring additional verification
* Users under review
* Special access groups
* Industry-specific categories
## List Properties and Behavior
**Key Characteristics:**
* Standard lists are mutually exclusive (one account, one standard list)
* Custom lists can have overlapping membership
* Standard lists are permanent and cannot be deleted
* Custom lists can be created and deleted as needed
* Lists can only contain account entities
**Important Notes:**
* Dashboard updates require an API call to reflect in your application
* Lists can be managed via both Dashboard and API
## List Hierarchy and Movement
When accounts are moved between lists through [Rules](/action/rules), they follow a specific hierarchy:
### Standard List Movement Rules
* **Block List** → Can move to **Main Account List** or **Allow List**
* **Main Account List** → Can move to **Allow List** only
* **Allow List** → Cannot be moved to other standard lists via "add to list" actions
### Custom Lists
* Accounts can be added to or removed from custom lists independently
* Custom list membership doesn't affect standard list hierarchy
* Multiple custom list memberships are allowed
## Managing Lists
### Dashboard Management
Use the Verisoul dashboard to:
* Create and manage custom lists
* Add or remove accounts manually
* Add or remove up to 10,000 accounts at once
* View list membership and statistics
For step-by-step instructions, see [Managing Lists](/dashboard/manage-lists).
### API Management
Use the Lists API to:
* Create and delete custom lists
* Add or remove accounts programmatically
* Retrieve list contents and metadata
* Integrate list management into your application workflows
### Authentication-based List Assignment
You can also add accounts to lists automatically during the authentication process by including a `lists` field in the account object:
```javascript theme={null}
{
"account": {
"id": "user-123",
"email": "user@example.com",
"lists": ["custom-list", "allow"]
},
"session_id": "00000000-0000-0000-0000-000000000001"
}
```
This approach allows you to categorize accounts at the time of authentication, making it easier to implement dynamic list assignment based on your application logic.
**Important:** If a list specified in the `lists` array does not exist, it will be automatically created. This ensures the API call will not fail due to missing lists. Use good naming conventions: lowercase with dashes or underscores, avoiding special characters or whitespace (e.g., `high-risk-users`, `beta_testers`).
## Best Practices
### List Organization
* Use clear, descriptive names for custom lists
* Document the purpose and criteria for each list
* Regularly review and clean up unused lists
* Consider list hierarchy when planning automation
### Account Management
* Monitor list movement patterns to identify trends
* Use custom lists for temporary categorizations
* Leverage standard lists for final fraud decisions
* Implement proper error handling for API operations
## Integration with Rules
Lists work seamlessly with [Rules](/action/rules) to create powerful automated fraud prevention workflows:
* Rules can automatically add accounts to lists based on conditions
* Rules can remove accounts from lists when criteria change
* Multiple rules can operate on the same lists simultaneously
* List changes are processed in real-time during API requests
***
Need help implementing lists? Contact our support team at [support@verisoul.ai](mailto:support@verisoul.ai)
# ML Models & Configuration
Source: https://docs.verisoul.ai/action/ml-models-configuration
Configuring Verisoul's machine learning models for your business
Verisoul's machine learning models are the core of our fraud prevention system. This guide explains how configuration changes directly affect the risk scores returned by our API endpoints.
## Understanding Model Configuration
Every customer starts with the same default configuration optimized for general fraud detection. When you make API calls to Verisoul's endpoints, the scores returned are calculated using your specific configuration. The Verisoul team can help tune these configurations to adjust how strict or lenient different signals are scored.
For example, configuration changes can:
* Lower the proxy risk score when a user is on Apple Private Relay
* Increase the risk score when a device is linked to multiple accounts
* Adjust how aggressively the API flags VPN usage
* Change scoring thresholds for bot detection
## Available Configuration Options
The following parameters can be configured to adjust your API response scores:
### Network Signals
* Proxy detection sensitivity
* VPN detection thresholds
* Datacenter IP scoring
* Tor exit node handling
* Impossible travel detection
### Device Signals
* Device fingerprinting strictness
* Cross-device linking thresholds
* Bot detection sensitivity
* Behavioral biometrics weighting
### Account Signals
* Multi-account detection thresholds
* Account linking sensitivity
* Email risk scoring for disposable/undeliverable domains
* Historical fraud patterns
## Configuration Process
Configuration changes are managed by the Verisoul team to ensure optimal performance. To request configuration updates:
1. Contact your Verisoul account representative
2. Describe your specific needs and use cases
3. Our team will analyze your requirements
4. We'll implement and test the changes
5. Monitor the results and adjust as needed
## Best Practices
When considering configuration changes:
* Start with default settings and monitor results
* Identify specific patterns of false positives/negatives
* Request targeted adjustments rather than broad changes
* Allow time to measure the impact of changes
* Consider your industry's unique fraud patterns
## Next Steps
* Explore [Lists](/action/lists) and [Rules](/action/rules) for additional fraud prevention
* Learn about [Blocking Bad Actors](/verifications/overview) with verifications
* Track scores, signals and anomalies over time in [Overview and Analytics](/dashboard/analytics)
# Action Overview
Source: https://docs.verisoul.ai/action/overview
Learn how to take action on Verisoul decisions
# Taking Action on Verisoul Decisions
After integrating Verisoul and receiving decisions on accounts - either through the API or dashboard - you'll need to implement strategies to block bad users while minimizing friction for legitimate users and eliminating false positives. Verisoul provides flexible options for implementing these decisions in your system.
## Implementation Approaches
There are three main frameworks for implementing Verisoul decisions:
1. **Real-time Automated Blocking via API**
2. **Manual Review and Offline Batch Blocking**
3. **Integration with Custom Fraud Models**
## Real-time Automated Blocking
Verisoul aims to automate the data science work needed for accurate user decisions. While Verisoul provides the decisions, you'll need to implement the actual blocking mechanism in your system. This is typically done alongside an authentication provider (e.g., Auth0's blocking API).
The decision field can be tuned in two primary ways:
### 1. ML Model Configuration
The machine learning models can be fine-tuned for your specific ecosystem:
* Adjust sensitivity for multi-accounting detection
* Configure proxy/VPN detection thresholds
* Customize device risk parameters
* Tune bot detection sensitivity
* Modify email risk scoring
* Calibrate how sub-scores contribute to the overall account score
Contact the Verisoul team to modify these configuration values. They can help evaluate different scenarios or analyze disposition data based on downstream metrics like chargebacks.
### 2. Lists and Rules
**Lists** help you organize accounts and enable specific workflows for different user groups. Verisoul provides several standard lists by default:
* **Allow List**: Marks accounts as Real
* **Block List**: Marks accounts as Fake
* **Main Account List**: Sets multiple\_accounts score to 0 (useful for allowing one primary account for users with multiple accounts)
**Important Notes about Lists:**
* Standard lists (allow, block, main\_account) are mutually exclusive
* Custom lists can contain overlapping accounts
* Lists can be managed via both dashboard and API
**Rules** provide real-time logic for automatically adding users to lists through a no-code interface. Rules consist of three components:
* **Triggers**: When to evaluate (first session vs all sessions)
* **Conditions**: Complex filtering logic using account/session data
* **Actions**: Add to or remove from lists
Key aspects of rules:
* Multiple rules can run concurrently
* Rules are versioned and maintain complete audit history
* Can operate on both account and session-level data
* Self-service rule creation in the dashboard, with backtesting (see [Building Rules](/dashboard/rules))
## Progressive Security with Step-up Verifications
To minimize friction for legitimate users while maintaining strong security, Verisoul supports a progressive security framework using step-up verifications. This approach allows you to:
1. Start with lightweight, frictionless checks for all users
2. Progressively add stronger verification requirements only when needed
3. Reduce false positives and support workload
4. Maintain security without compromising user experience
### Managed Verification Services
Verisoul provides several managed verification services that can be triggered based on risk signals:
* **Face Match**: Verify user identity through facial recognition
* **ID Check**: Validate government-issued identification documents
* **Phone Risk Detection**: Assess phone number risk and ownership
* **Email Risk Detection**: Evaluate email address authenticity and risk
These verifications can be implemented in various ways:
* **Proactive**: Required during initial signup for high-risk segments
* **Reactive**: Triggered when suspicious activity is detected
* **Progressive**: Added as users access more sensitive features
* **Risk-Based**: Applied based on risk score thresholds
For example, you might:
1. Allow initial access with basic email verification
2. Request phone verification if risk signals increase
3. Require ID verification for high-value transactions
4. Add face match for suspicious account patterns
This layered approach helps balance security and user experience while providing clear paths for users to verify their legitimacy when needed.
## Manual Review and Batch Processing
For cases where real-time blocking isn't required or when you want additional human oversight, you can:
* Review decisions in the Verisoul dashboard
* Export data for offline analysis
* Implement batch blocking processes
## Custom Fraud Model Integration
For organizations with existing fraud detection systems, Verisoul's decisions and signals can serve as additional inputs to enhance your models. This allows you to:
* Combine Verisoul's insights with your internal data
* Create custom scoring models
* Implement graduated response systems
## Next Steps
* Learn more about [ML Models Configuration](/action/ml-models-configuration)
* Explore [Lists](/action/lists) for organizing accounts
* Create automated workflows with [Rules](/action/rules)
* Set up [Verifications](/verifications/overview) for additional security
# Rules
Source: https://docs.verisoul.ai/action/rules
A comprehensive guide to Verisoul's automated rules engine for fraud prevention
# Rules in Verisoul
Rules enable real-time, automated decision-making by defining conditions that trigger list actions. They form the dynamic component of your fraud prevention strategy, allowing you to automatically respond to fraud patterns as they emerge.
This page explains how rules work. To create, backtest and publish them in the dashboard, see [Building Rules](/dashboard/rules).
## Understanding Rules
Rules consist of three main components that work together to automate fraud prevention decisions:
### 1. Triggers
Triggers define when a rule should be evaluated:
| Trigger Type | Description | Use Case |
| - | - | - |
| **Account Creation** | Evaluates only on the first session associated with an account | Account creation, initial signup screening |
| **All Sessions** | Evaluates on every session for an account | Ongoing monitoring, behavioral changes |
| **ID Check Completed** | Evaluates when an enrolled ID Check verification session completes | Act on document signals, ID risk flags, verification decisions |
| **Face Match Completed** | Evaluates when an enrolled Face Match verification session completes | Act on face-match results, device/network signals from the verification session |
**Note:** The `ID Check Completed` and `Face Match Completed` triggers require the session to be linked to an account at the time of rule evaluation. To achieve this, you can pass an `account_id` [when creating the session](/verifications/id-check/integration/initialize-session#account-id). Sessions without an account are not evaluated by rules; they return the raw model decision and have no list effects. Each trigger fires exactly once per verification session, when it completes.
### 2. Conditions
Conditions define the filtering logic that determines when a rule should fire.
**Available Fields:**
You can use any field available in the [Authenticate API response](/api-reference/session/authenticate) to create complex conditional logic. All fields on the account and session table in the dashboard are available to use in rules.
Example fields include:
* **Account Data**: `decision`, `account_score`, `bot`, `multiple_accounts`
* **Session Data**: `session.location.country_code`, `session.network.service_provider`, `session.proxy`
* **Email Intelligence**: `email.disposable`, `email.domain_type`, `email.email_score`
* **Network Signals**: `risk_signal_scores.proxy`, `risk_signal_scores.vpn`, `risk_signal_scores.ip_reputation` (-1 to 1), `session.network.asn`, `session.network.subnet`, etc.
* **Verification Data**: `verification.decision`, `verification.risk_score`, `verification.risk_flags`, and document signals such as `document_signals.is_id_digital_spoof` or the document's country
**Logical Operators:**
* **AND**: Fields from both account and session can be used
* **OR**: Currently OR cannot be nested inside an AND condition
* **Numeric Operations**: Scores are normalized to 0-100%
* **String Operations**: All string operations are case-insensitive
* **List Operations**: `is in`, `is not in` Can also be used on string fields
### 3. Actions
Actions define what happens when a rule's conditions are met:
| Action Type | Description | Effect |
| - | - | - |
| **Add to List** | Adds the account to a specified list | Moves account according to [list hierarchy](/action/lists#list-hierarchy-and-movement) |
| **Remove from List** | Removes the account from a specified list | Removes account from specified list only |
## Rule Management and Versioning
### Rule Versioning
* **Immutable History**: Every rule update creates a new version
* **Audit Trail**: Complete history of rule changes is maintained
* **No Deletion**: Rules can be archived but never permanently deleted
* **Ledger System**: Once a rule triggers for an account/session, it remains in the history forever
### Rule States
| State | Description | Behavior |
| - | - | - |
| **Enabled** | Rule is active and evaluating | Processes all matching triggers |
| **Disabled** | Rule is inactive | No evaluation occurs, but history is preserved |
| **Archived** | Rule is retired | No longer visible in active rules, but history remains |
### Rule Execution
**Processing Characteristics:**
* **Parallel Execution**: Multiple rules can trigger simultaneously on the same API request
* **Real-time Processing**: Rules are evaluated synchronously with API requests
* **Order Independence**: Rules execute in parallel, not sequentially
* **Conflict Resolution**: When multiple rules affect the same account, all actions are processed together
## List Movement Logic
### Standard List Hierarchy
Rules respect the standard list hierarchy when adding accounts to lists:
```
Allow List (highest priority)
↑
Main Account List
↑
Block List (lowest priority)
```
**Movement Rules:**
* **Block → Main Account**: ✅ Allowed
* **Block → Allow**: ✅ Allowed
* **Main Account → Allow**: ✅ Allowed
* **Allow → Block**: ❌ Not allowed via "add to list"
* **Allow → Main Account**: ❌ Not allowed via "add to list"
### Simultaneous Add/Remove Operations
When both add and remove actions occur in the same API request:
1. All remove actions are processed first
2. All add actions are processed second
3. Final list membership reflects the net result
**Example Scenario:**
* Account is on Allow List
* Rule A: Remove from Allow List
* Rule B: Add to Block List
* **Result**: Account ends up on Block List
## Rules, Lists, and Verifications
Verification products (ID Check, Face Match) use the same lists and the same rule builder as the core platform. There are no verification-specific lists or rules — one account, one set of lists. Because lists are shared, effects flow in both directions, by design:
### Verifications affect the account
A rule triggered by a verification acts on the **account**, not just the verification session. For example, a rule "IF `risk_signals` contains `id_age_below_16` → Add to Block List" places the account on the Block List. That membership affects the account everywhere, including core-platform decisions and all past and future verification responses.
### Account lists affect verification responses
An account's list membership — however it got there (manual add, core-platform rule, or verification rule) — overrides the `decision` returned by `verify-id` and `verify-face` and shown in the dashboard:
| List Membership | Verification `decision` |
| - | - |
| **Block List** | `Fake` |
| **Allow List** | `Real` |
`risk_score` and `risk_flags` are never modified — they always reflect the model's actual analysis. The `lists` field in the response shows the account's list membership, so you can tell when the decision was overridden.
## Common Use Cases
### Email-Based Rules
* Block disposable email providers
* Allow trusted corporate domains
* Allow test accounts in development environments
### Geographic Rules
* Block high-risk countries
* Allow specific regions
* Handle VPN/proxy detection
### ISP and Network Rules
* Block risky internet service providers
* Allow certain corporate vpn IP's
### Verification Rules
* Block accounts with IDs issued by specific countries
* Block accounts that present certain risk signals
* Block accounts whose ID shows an underage flag
* Clear a false verification result by allow-listing the account
# Delete Account
Source: https://docs.verisoul.ai/api-reference/account/delete
account-openapi.json DELETE /account/{account_id}
# Get Account
Source: https://docs.verisoul.ai/api-reference/account/get
account-openapi.json GET /account/{account_id}
# Get Account Sessions
Source: https://docs.verisoul.ai/api-reference/account/get-account-sessions
account-openapi.json GET /account/{account_id}/sessions
# Get Linked Accounts
Source: https://docs.verisoul.ai/api-reference/account/get-accounts-linked
account-openapi.json GET /account/{account_id}/accounts-linked
# Update Account
Source: https://docs.verisoul.ai/api-reference/account/update
account-openapi.json PUT /account/{account_id}
# Submit Email
Source: https://docs.verisoul.ai/api-reference/email-intelligence/post
email-openapi.json POST /email
This endpoint accepts an email for processing and returns a `request_id` for correlation. The full analysis result is delivered asynchronously through the `email.intelligence.completed` webhook. See the [Webhook Payload Reference](/email-intelligence/webhook-reference) for response fields and examples.
# Submit Email Batch
Source: https://docs.verisoul.ai/api-reference/email-intelligence/post-batch
email-batch-openapi.json POST /email
This endpoint accepts a batch for processing and returns a `batch_id` for correlation. Each email's full analysis result is delivered asynchronously through the `email.intelligence.completed` webhook. See the [Webhook Payload Reference](/email-intelligence/webhook-reference) for response fields and examples.
# Public Redirect
Source: https://docs.verisoul.ai/api-reference/face-match/public-redirect
facematch-openapi.json GET /public/liveness-redirect
This endpoint is meant for easy testing without having to do a backend integration.
Note that this is not the recommended integration method and is intended only for temporary testing purposes.
Since the redirect link is completely public, users can share it with others, which may lead to increased cost.
The Public Redirect endpoint should be used with caution and only for temporary testing. It is not suitable for long term use.
# Get Face Match Session
Source: https://docs.verisoul.ai/api-reference/face-match/session
facematch-openapi.json GET /liveness/session
# Verify Face
Source: https://docs.verisoul.ai/api-reference/face-match/verify-face
facematch-openapi.json POST /liveness/verify-face
# Verify Identity
Source: https://docs.verisoul.ai/api-reference/face-match/verify-identity
facematch-openapi.json POST /liveness/verify-identity
# Public Redirect
Source: https://docs.verisoul.ai/api-reference/id-check/public-redirect
idcheck-openapi.json GET /public/liveness-redirect
This endpoint is meant for easy testing without having to do a backend integration.
Note that this is not the recommended integration method and is intended only for temporary testing purposes.
Since the redirect link is completely public, users can share it with others, which may lead to increased cost.
The Public Redirect endpoint should be used with caution and only for temporary testing. It is not suitable for long term use.
The 'id=true' query parameter is required to include ID verification in the session.
# Get ID Check Session
Source: https://docs.verisoul.ai/api-reference/id-check/session
idcheck-openapi.json GET /liveness/session
The 'id=true' query parameter is required to include ID verification in the session.
# Verify ID
Source: https://docs.verisoul.ai/api-reference/id-check/verify-id
idcheck-openapi.json POST /liveness/verify-id
# Overview
Source: https://docs.verisoul.ai/api-reference/introduction
## Verisoul API
Verisoul provides a comprehensive set of APIs to help you integrate identity verification and fraud prevention into your applications. Below you'll find our core API sections.
Manage user accounts, retrieve account information, and delete accounts
Create, authenticate, and manage user sessions
Create and manage lists of accounts
Verify and validate phone numbers
Verify liveness and deduplicate accounts
Verify identity documents and match faces to ID photos
## Environments
Verisoul provides two environments for API access:
**Base URL:** `https://api.prod.verisoul.ai`
Use this environment for your live applications with real users.
**Base URL:** `https://api.sandbox.verisoul.ai`
Use this environment for development, testing, and integration.
Each environment requires its own API key. Make sure to use the appropriate API key for each environment.
## Authentication
All API endpoints are authenticated using secure API keys. You can find your API key in the [Verisoul Dashboard](https://dashboard.verisoul.ai) under **Settings → API Keys**. See [API Keys and Webhooks](/dashboard/api-keys-and-webhooks) for creating and rotating keys.
### Using Your API Key
Keep your API key secure and never expose it in client-side code. Your API key carries many privileges, so be sure to keep it secret.
To authenticate your API requests, include your API key in the request headers using the `x-api-key` header:
```bash theme={null}
curl -X GET "https://api.prod.verisoul.ai/list" \
-H "x-api-key: your-api-key-here"
```
# Add Account to List
Source: https://docs.verisoul.ai/api-reference/lists/add-account
list-openapi.json POST /list/{list_name}/account/{account_id}
# Create List
Source: https://docs.verisoul.ai/api-reference/lists/create
list-openapi.json POST /list/{list_name}
# Delete List
Source: https://docs.verisoul.ai/api-reference/lists/delete
list-openapi.json DELETE /list/{list_name}
# Get List
Source: https://docs.verisoul.ai/api-reference/lists/get
list-openapi.json GET /list
# Get All Accounts in List
Source: https://docs.verisoul.ai/api-reference/lists/get-all-accounts-in-list
list-openapi.json GET /list/{list_name}
# Remove Account from List
Source: https://docs.verisoul.ai/api-reference/lists/remove-account
list-openapi.json DELETE /list/{list_name}/account/{account_id}
# Phone Verification
Source: https://docs.verisoul.ai/api-reference/phone/post
phone-openapi.json POST /phone
# Rate Limits
Source: https://docs.verisoul.ai/api-reference/rate-limits
Verisoul implements rate limiting to ensure service stability and availability for all customers.
## Default Limits
By default, each project has the following rate limit: **900 requests per minute**.
This limit applies across all API endpoints for that project. When you exceed this limit, API requests return a `429 Too Many Requests` HTTP status code.
## How Rate Limiting Works
Rate limits are tracked per project across three windows. Every request must pass all three:
| Window | What it measures | Purpose |
| - | - | - |
| **1 second** | Requests in the current second | Caps instantaneous spikes |
| **6 seconds** | Requests in the last 6 seconds | Caps short bursts |
| **60 seconds** | Requests in the last 60 seconds | Enforces your overall rate limit |
Your allowed requests are counted over a sliding window (the last N seconds), not a fixed clock minute that resets at `:00`.
### Burstiness
Each short window allows more traffic than a perfectly even rate would. That headroom is the **burst factor**:
| Window | Burst factor | Meaning at the default 900 req/min limit |
| - | - | - |
| 1 second | **6×** | Up to \~90 requests in a single second (steady rate is 15/sec) |
| 6 seconds | **3×** | Up to \~270 requests in any 6-second span (steady rate is 90 / 6 sec) |
| 60 seconds | **1×** | Up to 900 requests in the last minute |
So you can send traffic in bursts, but not dump your entire minute quota at once.
### Soft limit near the ceiling
For the 6-second and 60-second windows, there is a soft zone above the limit (about **1.3×**). In that zone, requests are increasingly likely to be rejected. Past the hard ceiling, all requests are rejected.
If a request is rate limited, the response includes:
| Header | Description |
| - | - |
| `Retry-After` | Seconds to wait before retrying |
| `X-RateLimit-Limit` | Your maximum requests for the window |
Example response body:
```json theme={null}
{
"status": 429,
"error": "Rate Limit Exceeded",
"message": "Current rate limit is 900 requests per minute. Please try again later."
}
```
Space retries using the `Retry-After` header rather than immediately retrying. Immediate retries are more likely to stay rate limited.
## Increasing Your Rate Limits
If you require higher rate limits for your application, please contact us:
* **Slack**: Preferred contact method for the fastest response
* **Email**: [support@verisoul.ai](mailto:support@verisoul.ai)
When requesting increased limits, please provide:
* The `project_id` you are requesting a higher rate limit for
* The new rate limit you're requesting
* Justification for the increased limit (expected traffic volume, use case, etc.)
Typically rate limit increases are only approved for enterprise customers.
Our team will review your request and adjust your rate limits accordingly.
# Authenticate Session
Source: https://docs.verisoul.ai/api-reference/session/authenticate
session-openapi.json POST /session/authenticate
# Get Session
Source: https://docs.verisoul.ai/api-reference/session/get
session-openapi.json GET /session/{session_id}
# Account
Source: https://docs.verisoul.ai/api-reference/types/account
Account object structure and behavior
# Account Type
The Account object represents a user in the Verisoul system. Each account has a unique identifier, optional identity attributes (email, phone, name, username), and customizable metadata.
## Structure
| Field | Type | Required | Description |
| - | - | - | - |
| `id` | string | Yes | Unique identifier for the account |
| `email` | string | No | Email address associated with the account |
| `phone` | string | No | Phone number on file for the account, in E.164 format (e.g. `+14155550123`). Used for [multi-accounting phone matching](/signals-scores/multi-accounting). |
| `first_name` | string | No | Account holder's first name. Combined with `last_name` for multi-accounting name matching. |
| `last_name` | string | No | Account holder's last name. Combined with `first_name` for multi-accounting name matching. |
| `username` | string | No | Username associated with the account. Used for multi-accounting username matching. |
| `expected_countries` | array | No | Countries the account is expected to legitimately operate from, as ISO 3166-1 alpha-2 codes (e.g. `["US", "CA"]`). |
| `metadata` | object | No | Key-value pairs of custom data associated with the account |
| `group` | string | No | Groups must be enabled for the project to use this field. See [Multi Accounting Groups](/integration/advanced/multi-accounting-groups) for more information. |
| `lists` | array | No | Array of list names to add the account to during authentication. See [Lists](/action/lists) for more information. |
## Email Address
An Account can have have a string email address. Verisoul stores only one email address per Account. When updating an account with a new email address, the previous email address is replaced.
## Phone Number
An Account can have one phone number, provided in E.164 format (e.g. `+14155550123`). Like email, Verisoul stores one phone number per Account — updating an account with a new phone number replaces the previous one, and omitting the field keeps the stored value.
Before matching, the number is normalized: formatting characters (spaces, dashes, parentheses, dots) are stripped, and the result must be a valid E.164 number (`+` followed by 8–15 digits). Two accounts sharing a normalized phone number are linked with the full-strength `phone` match type. See [Multi-Accounting](/signals-scores/multi-accounting) for details.
## Username
An Account can have one username. Updating an account with a new username replaces the previous one, and omitting the field keeps the stored value.
Before matching, the username is lowercased and only letters, digits, `.`, `_`, and `-` are kept — so `Jane.Doe` and `jane.doe` match, while `jane.doe` and `janedoe` remain distinct. Two accounts sharing a normalized username are linked with the full-strength `username` match type.
## Name
An Account can have a first and last name, passed as separate `first_name` and `last_name` fields. Each field is updated independently — a call that includes only `first_name` keeps the stored `last_name`.
For matching, the available parts are combined into a single name and normalized: lowercased, with punctuation and diacritics removed (so `Jane O'Brien` and `JANE obrien` match). Providing either part enables name matching. Because names are far less distinctive than phone numbers or usernames, a name match is moderate strength: a name match alone never fully links two accounts, but name plus another match (device, network, email) does.
## Expected Countries
The `expected_countries` field is a declaration of where the account should legitimately operate from. Sending a **non-empty** list replaces the entire stored array (so a country can be removed by sending a new list without it). Omitting the field or sending an empty list `[]` leaves the stored declaration unchanged.
Once declared, sessions outside the expected countries carry the `outside_expected_countries` risk signal and raise the [location spoofing score](/signals-scores/location#location-spoofing-score) in proportion to how far outside they are. See [Expected Countries](/signals-scores/expected-countries) for how both work.
## Metadata
The metadata object allows you to store custom data with each account. When updating metadata:
1. New fields are added to the existing metadata
2. Existing fields are overwritten with new values
3. Other existing fields are preserved
### Example: Metadata Updates
Starting metadata state:
```json theme={null}
{
"id": "acc_123456789",
"email": "user@example.com",
"metadata": {
"plan": "free",
"signup_date": "2023-01-15"
}
}
```
**Update 1: Overwrite an existing field**
```json theme={null}
// Request
{
"metadata": {
"plan": "premium"
}
}
// Resulting account state
{
"id": "acc_123456789",
"email": "user@example.com",
"metadata": {
"plan": "premium", // Updated
"signup_date": "2023-01-15" // Preserved
}
}
```
**Update 2: Add a new field**
```json theme={null}
// Request
{
"metadata": {
"referral_code": "FRIEND50"
}
}
// Resulting account state
{
"id": "acc_123456789",
"email": "user@example.com",
"metadata": {
"plan": "premium", // Preserved
"signup_date": "2023-01-15", // Preserved
"referral_code": "FRIEND50" // Added
}
}
```
## Lists
The `lists` field allows you to specify which lists an account should be added to. This field is available in both:
* **Server-side API**: During authentication via `/session/authenticate`
* **Client-side SDK**: Using the `Verisoul.account()` method
This is useful for automatically categorizing accounts based on certain criteria.
### Example: Adding Account to Lists
**Server-side (Authenticate API):**
```json theme={null}
{
"account": {
"id": "niel-test-08-11-2025",
"email": "niel30@beeble.com",
"lists": ["custom-list", "allow"]
},
"session_id": "00000000-0000-0000-0000-000000000001"
}
```
**Client-side (Browser SDK):**
```javascript theme={null}
await window.Verisoul.account({
id: "niel-test-08-11-2025",
email: "niel30@beeble.com",
lists: ["custom-list", "allow"]
});
```
**Important Notes:**
* The account will be added to all specified lists during processing
* If a list does not exist, it will be automatically created (this will not cause the API to fail)
* Use good naming conventions: lowercase with dashes or underscores, no special characters or whitespace
* Standard lists (allow, block, main\_account) follow the [list hierarchy rules](/action/lists#list-hierarchy-and-movement)
* Custom lists can be used for flexible account categorization
```
```
# Email
Source: https://docs.verisoul.ai/api-reference/types/email
Email object structure used in the Email Intelligence endpoints
# Email Type
The Email object is the request body for the [Submit Email](/api-reference/email-intelligence/post) and [Submit Email Batch](/api-reference/email-intelligence/post-batch) endpoints. It contains the email address to analyze, an optional flag to enable identity intelligence, and optional identity claims.
## Structure
| Field | Type | Required | Description |
| - | - | - | - |
| `email` | string | Yes | The email address to analyze |
| `identity_intelligence` | boolean | No | When `true`, enables deeper identity analysis including online presence detection, connected identity discovery, and claims verification. Defaults to `false`. |
| `claims` | object | No | Identity claims to verify against data linked to the email. Only meaningful when `identity_intelligence` is `true`. |
## Claims
Claims represent information your customer has provided that you want to verify against independent data sources. When `identity_intelligence` is enabled, Verisoul matches these claims against identity data linked to the email and reports whether each claim matches, partially matches, or does not match.
| Field | Type | Description |
| - | - | - |
| `name` | object | The expected name of the email owner. You can provide `first`, `last`, or both. |
| `name.first` | string | First name (e.g., `"John"`) |
| `name.last` | string | Last name (e.g., `"Doe"`) |
| `phone` | string | Phone number in a valid format. E.164 is recommended. |
| `country` | string | Two-letter country code ([ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)). |
### Name
You can provide a first name, a last name, or both. Verisoul compares the provided name(s) against names associated with the email across data sources.
```json theme={null}
{ "name": { "first": "John", "last": "Doe" } }
```
```json theme={null}
{ "name": { "first": "John" } }
```
```json theme={null}
{ "name": { "last": "Doe" } }
```
### Phone
Provide the phone number in a standard format. [E.164](https://en.wikipedia.org/wiki/E.164) (international with `+` prefix) is recommended, but national formats are also accepted.
**Examples:**
| Format | Example |
| - | - |
| E.164 (recommended) | `"+14155551234"` |
| E.164 with country code | `"+442071234567"` |
| National (US) | `"(415) 555-1234"` |
| National (UK) | `"020 7123 4567"` |
### Country
Provide the expected country as a two-letter [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code.
**Examples:**
| Code | Country |
| - | - |
| `"US"` | United States |
| `"GB"` | United Kingdom |
| `"CA"` | Canada |
| `"DE"` | Germany |
| `"JP"` | Japan |
| `"BR"` | Brazil |
## Usage
### In Submit Email
Claims and `identity_intelligence` are provided at the top level alongside the email:
```json theme={null}
{
"email": "john.doe@example.com",
"identity_intelligence": true,
"claims": {
"name": { "first": "John", "last": "Doe" },
"phone": "+14155551234",
"country": "US"
}
}
```
### In Submit Email Batch
`identity_intelligence` is set at the top level and applies to all emails. Claims are provided **per email**, allowing you to verify different identities in a single batch:
```json theme={null}
{
"emails": [
{
"email": "john.doe@example.com",
"claims": {
"name": { "first": "John", "last": "Doe" },
"country": "US"
}
},
{
"email": "jane@company.com",
"claims": {
"name": { "first": "Jane" },
"phone": "+442071234567"
}
}
],
"identity_intelligence": true
}
```
# Changelog
Source: https://docs.verisoul.ai/changelog
New features and improvements across the Verisoul API, SDKs, and dashboard
Everything we ship that changes what you can see or do.
## Exclude personal data from Verify ID
Add `?excludePII=true` to `POST /liveness/verify-id` and the response leaves out `document_data`, `photo_urls`, and `video_urls`. Everything else is unchanged. Without the parameter you get the full response, as before. Webhooks are not affected.
## Move accounts between lists, and update a list description
`PATCH /list/{list_name}` updates the description on a list.
`POST /list/{list_name}/account/move` moves up to 1,000 accounts to another list in one call, and returns an outcome per account, such as `moved`, `conflict`, or `not_found`.
## Name signals on ID Check
Document signals include `name_not_found`, which fires when the name is missing from the document, and `name_partially_missing`, which fires when only part of it is found. Both work as rule conditions.
## Authenticate rejects a body that is missing an account
`POST /session/authenticate` returns `400` when the request body is missing a usable `account` object, for example when the keys arrive capitalized as `Account` and `Session_id`. These requests returned `500` before, so error handling keyed on the status code sees the new value.
## Account grids load in seconds
Opening a list's accounts on a large project takes seconds. Measured on a project with 75 million accounts: the block list page at 3.5 seconds, down from 100 to 300 seconds, an unfiltered page at 2.6 seconds, a country filter at 2.8 seconds, and a 4.2 million member list at 3.3 seconds.
## Analyze with AI on ID Check and Face Match
The AI investigation panel runs on verification sessions. The liveness video analysis and the captured photos feed into it.
On accounts, the analysis reads your project's own scoring thresholds, the definitions of the lists the account is on, and the conditions of the rules it hit.
## Incomplete verification sessions
ID Check and Face Match sessions that ended before they completed open from the grid. Photos and videos captured up to that point are in their tabs, and the sections that need a finished session carry a placeholder explaining what is missing.
## Dashboards for every customer
Dashboards is in the sidebar for every customer, and you can build your own. Charts and tables built in an Agent chat can be added to them.
## Digital IDs on ID Check
A digital ID comes back as a risk signal, and you can write rules on it. When a digital ID is on screen, the video analysis view shows the clips that apply to it.
## Rule building
Domain Type, Country, and Mobile Languages offer value suggestions on **contains** conditions, with exact matches ranked first, so typing `us` gives United States.
School ID and employee ID are available as document templates in rules.
## Numeric rule conditions require a value
Conditions using `>`, `>=`, `<`, and `<=` match when the field they read has a value. A rule on `verification.id_age < 18` matches only when the document carries a readable date of birth. Missing values used to compare as zero, so rules of this shape match fewer accounts and sessions from now on.
[Rules](/action/rules)
The sessions table has a **Source** column, filterable by web, iOS, and Android, and a **Source Version** column you can turn on from the column panel. Both values show in the session side panel and come along in CSV exports.
You can write rules on session source.
Sessions from the iOS, Android, and Flutter SDKs now report the SIM carrier and the country that carrier operates in. Carrier country comes from the device, so it holds up when the IP is proxied.
Every session returns `source` (`web`, `ios`, or `android`) and `source_version`, so you can tell which integration produced a session and which SDK version it ran.
Linked accounts return `first_seen` and `last_seen`, showing when two accounts were first connected and when that link was last active.
## Account timestamp format
`first_seen` and `last_seen` come back as normalized ISO 8601 timestamps, on the account itself and on linked accounts. Earlier responses passed through nanosecond precision, for example `2026-04-22T21:38:47.806000000Z`, so anything parsing these fields sees a different string from now on.
[Account object](/api-reference/types/account)
## Sessions with an out of range country code
A session persists even when network intelligence reports a country code outside the two letter form, such as `CN-TW` for Taiwan. The country code on that session is left unset, and the account behind it gets its scores, decision, and `last_seen` as usual. Sessions like these were dropped before, so they now appear in the sessions table and in Get Session.
## Rules and lists act on verification results
Two rule triggers are live in the dashboard: **ID Check Completed** and **Face Match Completed**. Rules on these triggers can read the verification decision, risk score, risk signals, document country, ID age, and the rest of the verification response.
Rules apply to the account behind the verification. A rule like `IF risk signals contains id_age_below_16 → Add to Block List` adds the account itself, tied to the session where the condition matched.
List membership now flows through to verification decisions. Block-listed accounts return `Fake` on `/verify-id` and `/verify-face`, allow-listed accounts return `Real`. Both endpoints return `lists` and `rules` fields showing what was applied.
If you already keep accounts on lists, some verification decisions will change to match the account decision.
## Automatic enrollment
Pass an `account_id` when you create a verification session and we enroll the account once the session completes. Enrollment was previously a separate call that had to happen in the right order, and sessions that skipped it returned confusing `Fake` results.
[Rules](/action/rules) · [Face Match session setup](/verifications/face-match/integration/initialize-session) · [ID Check session setup](/verifications/id-check/integration/initialize-session)
## Rule suggestions from the agent
Ask the dashboard agent for a rule and it proposes conditions backed by backtest numbers from your own data, then hands off to the rule create form with those conditions filled in. Suggestions arrive disabled and the backtesting drawer runs on arrival, so you see what the rule would have caught before you turn it on.
## Online presence signals on email
Email intelligence reports online presence in tiers. `no_online_presence` and `sparse_online_presence` come back as risk signals, `moderate_online_presence` and `strong_online_presence` as trust signals. All four work as filters on the email table. Rows scored before this change stay filterable on `online_presence_detected`.
[Email Intelligence](/email-intelligence/overview)
## Risk signal details
Sessions return `risk_signal_details`, a set of named reason codes that explain why a risk score is elevated. It comes back on Authenticate, Unauthenticated, and Get Session.
* **Network**: 13 signals covering the proxy, VPN, and location scores, such as `ip_on_vpn_list` and `true_country_mismatch`.
* **Device**: 22 signals covering device risk, such as `browser_tampering_detected` and `android_emulator_or_rooted`. This includes emulator and jailbreak detection, factory reset boot profiles, Play Integrity verdicts, and device brand signals.
Signals stay consistent with the scores beside them. A clean proxy score carries no proxy signal, and an elevated score always carries at least one signal explaining it. A few factual signals fire independently of score level, so you can write rules directly against them.
## Mobile device facts
Sessions from the iOS and Android SDKs return a `mobile` block: brand, model, RAM, boot count, uptime, battery, languages, and OEM integrity verdicts. Native sessions also improve the existing `device` fields with values a WebView masks or misreports.
All of it works in rules, as filterable columns in the sessions table, and in the session side panel. You may need to enable the new columns to see them.
[Risk Signals](/signals-scores/risk-signals) · [Mobile](/signals-scores/mobile)
## Android time settings signal
Device risk signals include `android_manual_time_settings`, which fires when a device has automatic time or automatic time zone turned off. It works in rules and as a filter on the sessions table.
## Duplicate a rule, and create rules disabled
You can duplicate an existing rule, which opens the create form with its conditions and actions filled in.
The create and edit forms carry a **Status** switch, so you can save a rule disabled and turn it on once you have reviewed it. New rules default to enabled.
## Equals and not equals on lists
Rules on the Lists field take **Equals** and **Not equals**. Matching is on exact list membership, so a condition on `advertiser 10` treats `advertiser 100` as a separate list.
## Account fields in the accounts table
The accounts table shows a **Phone** column, with **Username**, **First Name**, **Last Name**, and **Expected Countries** available from the column panel. All five filter, and the filters sync to the URL.
## Share a view, and clear a custom date range
Tables and analytics pages have a Share button beside the date picker. It copies the current URL, which already carries the date range and the filters you have applied.
An active custom date range gets an X beside it that clears the range and returns the page to the default 7 day period.
[Rules](/action/rules)
Five optional fields are accepted on the account object, through Authenticate Session and Update Account: `phone`, `first_name`, `last_name`, `username`, and `expected_countries`.
**Phone, name, and username feed multi-accounting.** They become additional identifiers for linking accounts. A shared phone number or username is a full match on its own. A shared name supports links alongside device, network, and email matches. Matches appear in linked accounts with their match type. Values are normalized behind the scenes, so `+1 (415) 555-0123` matches `+1-415-555-0123` and `José García` matches `jose garcia`.
**Expected countries feed location spoofing.** Declare where an account should be operating, for example `["US", "CA"]`. Sessions from outside those countries, or far from them, factor into that account's location spoofing score.
All five fields are optional and additive. Send nothing and behavior stays the same. Updating a field later works as you would expect, with last write winning per field.
[Account object](/api-reference/types/account) · [Multi-Accounting](/signals-scores/multi-accounting) · [Expected Countries](/signals-scores/expected-countries)
## Upgraded real-time models
We improved how device and network data is collected. Measured against the previous models:
* 31% better catch rate on sophisticated proxies and anti-detect browsers
* 10 to 14% fewer false positives on location spoofing and proxy/VPN, with the largest gains on slow networks
* 18 to 20% faster session IDs and API responses
## AI agent in the dashboard
There is an agent in the bottom-left corner of the dashboard. It has access to your project data in a privacy-preserving sandbox, which is never shared with model providers or used for training. It can run investigations, build charts, slides, and CSVs, and take actions such as adding accounts to lists.
Prompts worth trying:
* "Pull all accounts linked to at above 50% strength, three levels deep"
* "Compare fake and suspicious users over the last 7 days against the prior 7. What is driving the change?"
* "Here are 100 emails. Find their accounts and add them to the block list."
* "Build a report on the last 30 days: fraud rates, key drivers, trends"
The agent keeps per-user and per-project memory, so context you give it in one chat carries into later ones. You can react to its messages with feedback, which we review weekly.
## Face Match and ID Check
We rebuilt the data processing and risk engine behind both products, and we now capture and return session video in the dashboard.
* 30% more accurate ID data capture
* 82% fewer false positive risk flags, while catching more fraud
* Video makes phone handoffs, digital IDs, and background characteristics visible during review
* ID Check returns a face age estimate on the verification response.
* Network intelligence identifies satellite connections as a distinct connection type.
* The `id_age` document signal description now states what it measures: the age derived from the date of birth on the document.
* Email intelligence groups the Identity Intelligence signals together and marks them as such in the API reference.
Face Match and ID Check responses include a URL for the recorded session video. The same video is available in the dashboard on the verification detail view.
Video shows the context around a capture: a phone handed between people, an ID held up on another screen, and the surroundings the capture happened in.
You can serve the Verisoul browser SDK from a subdomain you own, for example `verify.yourcompany.com`. Requests go to your domain and reach us behind it.
Setup is a nameserver delegation on a subdomain you dedicate to us. We handle the certificate and keep it renewed.
Traffic to a first-party domain gets past ad blockers and tracker blocklists, which raises the share of sessions that produce a full signal set.
[Custom Hostname](/integration/custom-hostname)
Sessions and accounts return a `location_spoofing` score. It combines the signals we collect about where a session claims to be and where it appears to actually be: timezone, locale, GPS where available, IP geolocation, and network routing.
The score is in the API reference for Get Session and Get Account, in the sessions and accounts tables, and available in rules.
[Get Session](/api-reference/session/get) · [Network Intelligence](/signals-scores/network)
Email intelligence used to run only as part of a real-time session through `/session/authenticate`. It is now a standalone endpoint, so you can submit an email on its own and get a full assessment back.
Email carries history, and history takes years to fake. It also links out to names, phones, and locations, which adds identity context to device and network signals.
What comes back:
* **Risk score** from -1 (high trust) to +1 (high risk), with a decision label of `high_trust`, `trusted`, `moderate`, `risky`, or `high_risk`, plus the trust and risk signals behind it.
* **Online history and age**: how long the address has existed, where it has appeared, and breach history with dates.
* **Social signals**: which platforms the address is registered on, such as Google, Facebook, and LinkedIn.
* **Deliverability**: whether the address can receive mail, with a recommendation of `send`, `send_with_caution`, or `do_not_send`.
* **Domain intelligence**: disposable, fresh, and fake domains, classified as personal, business, education, government, relay, or inactive.
* **Identity intelligence**: pass an expected name, phone, and country, and we match those claims against external data, then return connected names and phone numbers.
Batches of up to 1,000 emails go through the API or a CSV upload in the dashboard. Results arrive by webhook and appear in the dashboard within a few minutes.
Email Intelligence replaces Manual Lookup. Data from Manual Lookup stays in the email table.
[Email Intelligence](/email-intelligence/overview)
# Accounts and Sessions
Source: https://docs.verisoul.ai/dashboard/accounts-and-sessions
Find accounts and sessions, and investigate the signals behind an account decision
The **Investigate** section holds the project's records. **Accounts** lists each account with its decision, scores, lists and rule hits. **Sessions** lists each session with the network, device and location Verisoul observed. Every account opens into an investigation page that brings its signals, linked accounts, rule hits and sessions together.
For what each score and signal means, see [Scoring Overview](/signals-scores/scoring-overview) and [Account vs Session](/signals-scores/account-vs-session).
## Tables
| Table | Date range applies to | Notes |
| - | - | - |
| **Accounts** | **Last Seen** | Accounts active in the period |
| **Sessions** | **Created At** | Covers the last 89 days |
Quick search on **Accounts** matches account ID, email, multi-accounting group, phone and username. On **Sessions** it matches a full session ID, part of an account ID, or the account's email. A search covers every date the table holds, regardless of the date range. To find sessions by IP address, filter the **Network IP** column.
**Ask AI** in the search box turns a sentence into filters and a sort. See [AI Agent](/dashboard/ai-agent).
## Downloads
Downloads include every row that matches the current filters, date range and search, up to 5,000,000 rows, with all fields including hidden columns and account metadata. Downloading needs **Export data** (Analyst and above).
## Bulk actions
Selecting rows on **Accounts** enables:
| Action | Limit per operation | Guide |
| - | - | - |
| **Add to List** / **Remove from List** | 10,000 accounts | [Managing Lists](/dashboard/manage-lists) |
| **Delete account(s)** | 100 accounts | [Deleting Account Data](/dashboard/delete-account) |
Selecting all rows applies the action to every account matching the current view, up to the limit.
## Account investigation
The account page shows the account's score, decision, identifiers, lists, metadata, first and last seen dates, and countries. From here you can **Analyze with AI**, add or remove the account from lists, or delete it.
### Account Signals
**Risk Flags** groups the account's signals by multi-accounting, bot, device, network and volatility. A flag is high above 66% and moderate above 33%.
Volatility flags use counts:
| Flag | Moderate | High |
| - | - | - |
| Devices in the last 7 days | 3 or 4 | 5 or more |
| Networks in the last 7 days | 7 to 12 | 13 or more |
| Countries | 2 or 3 | 4 or more |
**Email Signals** shows the email's risk score, domain details, and trust and risk signals. **Run Email Intelligence** submits the address for deeper analysis, which is billable. See [Email Intelligence](/dashboard/email-intelligence).
**Multi-Accounting** shows linked accounts:
* The graph draws the 50 strongest links. The table beside it lists up to 1,000.
* A dashed line marks an account linked only by Apple Device Check. These are left out of **Total Linked**.
* **Linked to blocked user** is true when a linked account is on a list whose name contains "block" or "banned".
See [Multi-Accounting](/signals-scores/multi-accounting) for how links are made.
**Hit Rules** lists the 100 most recent rule hits for the account.
### Session Signals
The account's sessions from the last 89 days (up to 1,000), a map of their IP locations, and breakdowns of top ISPs, risky network connections, devices, languages, time zones and browsers. Risky network connections are sessions with a bot, proxy, VPN or datacenter score above 50%.
### Verifications
For organizations with verifications, lists the account's ID Check and Face Match sessions. See [Reviewing Verifications](/dashboard/verifications).
## Session details
A session opens in a side panel with its source (web, iOS or Android) and SDK version, risk flags and risk signals, rule hits, location, network, device, mobile details for app sessions, and behavior counts such as clicks, key presses and touches.
## Search
**Search** in the sidebar, or the **/** key, finds accounts across the project by ID, email, phone or username.
## Permissions
| Action | Viewer | Analyst | Editor |
| - | - | - | - |
| View accounts and sessions | ✓ | ✓ | ✓ |
| Download CSV files | | ✓ | ✓ |
| Change custom lists | | ✓ | ✓ |
| Change **Allow**, **Block** and **Main Account** | | | ✓ |
| Delete account data | | | ✓ |
| **Run Email Intelligence** | | | ✓ |
Admins have every permission. See [Managing Users](/dashboard/managing-users).
## Need Help?
If you have any questions about accounts and sessions, please contact our support team at [support@verisoul.ai](mailto:support@verisoul.ai).
# AI Agent
Source: https://docs.verisoul.ai/dashboard/ai-agent
Ask questions about a project in plain English and get answers, charts, files and list changes back
The AI Agent answers questions about your Verisoul data. It writes and runs its own queries across accounts, sessions, verifications, rules and lists, and returns text, charts, tables or files. It can also draft rules and change list membership, and it asks for approval before any change that affects live decisions.
It appears in three places:
| Where | What it does |
| - | - |
| **AI Agent** in the sidebar | Full chats about a project, kept in your chat history |
| **Analyze with AI** on an account, ID Check or Face Match session | An analysis of that record, with follow-up questions |
| **Ask AI** in the Accounts and Sessions tables | Turns a sentence into filters and a sort |
The agent works in a private sandbox for each project, and your data is never used to train models.
## Scope
Each chat reads one project, chosen before the first message and fixed after it. The agent can use:
* Accounts and sessions, with their scores, signals and list membership
* Rules, their definitions, and every time each one fired
* Verifications, including ID Check and Face Match photos
* Live state from the Verisoul API, such as a list's current members
* The Verisoul documentation, for product and integration questions
Mention records with `@`, paste up to 1,000 IDs in one batch, or attach files (up to 20 per message, 25 MB each).
Prompts worth trying:
* *Pull all accounts linked to `{account_id}` at above 50% strength, three levels deep*
* *Compare fake and suspicious users over the last 7 days against the prior 7. What is driving the change?*
* *Here are 100 emails. Find their accounts and add them to the block list.*
## Outputs
| Output | Notes |
| - | - |
| Charts and tables | Downloadable, and can be added to a [custom dashboard](/dashboard/dashboards), where they re-run on live data |
| Files | CSV and Excel exports, slide decks, Word documents and PDFs |
| Rule suggestions | Drafted from your data, with the number of accounts they would match today |
The agent does not create or turn on rules. **Review in Rules** opens the [rule builder](/dashboard/rules) with the suggestion filled in and backtesting running, and you save it from there.
## Changing lists
The agent can create lists and add or remove accounts, from pasted IDs, attached files, or its own findings.
| Change | Behavior |
| - | - |
| Custom list, up to 1,000 accounts | Runs immediately |
| **Allow**, **Block** or **Main Account** | Waits for approval |
| Any list, more than 1,000 accounts | Waits for approval |
An approval card lists the change. **Approve** runs it and **Reject** discards it.
List changes through the agent need the **Change Allow and Block lists** permission (Editor and Admin). For other roles, chats are read-only. Analysts can still manage custom lists from the **Lists** page.
## Analyze with AI
On an account, the analysis reads your project's scoring thresholds, the lists the account is on, and the rules it hit. On a verification session, it also reads the captured photos and the session video. **Open full chat** continues the conversation in **AI Agent**. Incomplete verification sessions cannot be analyzed.
## Memory
The agent keeps notes between chats:
* **Per person:** stated preferences, such as preferred chart types or recurring breakdowns
* **Per project:** what it has learned about the project's data and fraud patterns, shared with everyone who uses the agent on that project
Personal data and credentials are not stored in memory.
## Sharing
Only the person who started a chat can share it.
| Option | Who can see the chat |
| - | - |
| **Share with project** | Everyone with access to the project, read-only. They can continue in their own copy. |
| **Share publicly** | Anyone with the link, without signing in, including files and messages added later |
Turning off **Share publicly** disables the link immediately. Turning it on again creates a new link.
## Scheduled runs
A prompt can run on a schedule through the [MCP server](/dashboard/mcp-access), using `agent_schedules_create` with a cron expression in UTC or as a manual schedule. Each run opens a new chat for the schedule's creator and uses that person's permissions. Creating schedules needs **Schedule agent runs** (Editor and Admin).
## Permissions
| Action | Viewer | Analyst | Editor |
| - | - | - | - |
| Chat, **Analyze with AI**, **Ask AI**, share chats | ✓ | ✓ | ✓ |
| Add charts and tables to dashboards | | ✓ | ✓ |
| Change lists | | | ✓ |
| Schedule runs | | | ✓ |
Admins have every permission. See [Managing Users](/dashboard/managing-users) for custom roles.
## Need Help?
If you have any questions about the AI Agent, please contact our support team at [support@verisoul.ai](mailto:support@verisoul.ai).
# Overview and Analytics
Source: https://docs.verisoul.ai/dashboard/analytics
Track trust tiers, risk signals and anomalies over time, and open the records behind any number
**Overview** is a project's home page: headline volumes, trust tiers, signal trends and geography for the selected period. **Analytics** breaks the same data into filterable tabs and highlights what changed against the previous period. Both are read-only. For charts you define yourself, see [Custom Dashboards](/dashboard/dashboards). For what the scores mean, see [Scoring Overview](/signals-scores/scoring-overview).
Every change figure compares the selected period with the period of the same length just before it.
## Overview
| Section | What it shows |
| - | - |
| Headline tiles | **All accounts** (accounts with a session in the period), **New accounts** (first seen in the period), **All sessions**, and **Verifications** and **Emails** when there is activity |
| **Accounts by trust tier** | Daily **Real**, **Suspicious** and **Fake** counts, for all active accounts or new accounts only |
| **Account Signals** / **Session Signals** | The share of accounts or sessions scoring above 30% on each signal, such as multi-accounting, bot, proxy, VPN and location spoofing |
| **Geography** | Account volume by country, with each country's trust mix and share of **Fake** accounts |
| Verification and email charts | Daily ID Check and Face Match results by trust tier, and email results by risk band |
Session volumes are pre-computed and can lag the most recent few minutes of traffic.
## Analytics
| Tab | Contents | Filters |
| - | - | - |
| **Accounts** | Trust tiers, risk signals with an adjustable threshold, risk flags, multi-accounting cluster sizes, sign-up anomalies | List, Group, Domain, Country |
| **Sessions** | Session signals, session anomalies | Country, OS, Browser, Network type, ISP |
| **Verifications** | ID Check and Face Match pass rates, completion rate, results, anomalies | Country, OS, Browser, Network type, Document type |
| **Email** | Risk bands, disposable domain rate, email anomalies | Domain, Country |
The **Verifications** tab appears for organizations that use verifications, and the **Group** filter for projects that use [Multi Accounting Groups](/integration/advanced/multi-accounting-groups).
**Multi-accounting clusters** groups new accounts by how many accounts they link to (1, 2-3, 4-5, 6-10, 11-25, 26-50, 51+). See [Multi-Accounting](/signals-scores/multi-accounting) for how links are made.
## Anomalies
Each tab ends with anomaly tables grouped by dimension, such as domains, countries, ISPs or risk flags. For each value, Verisoul compares its **share** of the tab's traffic in the selected period with its share in the previous period. Using shares keeps overall growth from flagging every value: a domain growing with your traffic stays quiet, while a small domain that triples its share stands out.
| Pill | Meaning |
| - | - |
| **Volume** | Change in the value's share of all traffic |
| **Risk Volume** | Change measured among Suspicious and Fake records only |
| **New** | No records in the previous period |
The pill color gives the verdict: red for a significant change in a risky direction, green for a significant improvement, blue for a traffic shift with a steady trust mix, and grey for no significant change.
Detection needs volume:
* Both periods need at least 500 records on the tab after filters. Below that, groups read **Waiting on more volume**.
* A value needs at least 20 records across both periods to be flagged.
Email anomalies map risk bands to trust tiers: **High trust** and **Trusted** count as Real, **Moderate** as Suspicious, **Risky** and **High risk** as Fake.
## Drilldowns
Clicking a bar, line point or table row on Analytics offers to open the matching records in the Accounts, Sessions, ID Check, Face Match or Email table, filtered to that value and period. A click on a time series narrows the table to that day. Items with no matching table column open without a panel.
## Permissions
Overview and Analytics need **View analytics**, which every project role holds. Opening the records behind a number uses the permissions of the table it opens. See [Managing Users](/dashboard/managing-users).
## Need Help?
If you have any questions about Overview and Analytics, please contact our support team at [support@verisoul.ai](mailto:support@verisoul.ai).
# API Keys and Webhooks
Source: https://docs.verisoul.ai/dashboard/api-keys-and-webhooks
Manage project API keys and signed webhooks
API keys let your backend call the Verisoul API for one project. Webhooks send a project's events to your endpoint, signed so you can verify they came from Verisoul. Both are managed under **Settings**, in tables covering every project you can access.
The keys on this page are project API keys. Keys for AI clients are separate; see [MCP Access](/dashboard/mcp-access).
## API keys
A project API key has full access to the Verisoul API for its project, including the **Allow** and **Block** lists. Send it in the `x-api-key` header, as shown in the [API Reference](/api-reference/introduction). Client SDKs identify the project by its Project ID and never need a key.
| Property | Detail |
| - | - |
| Scope | One project, in one environment |
| Format | 40 characters of letters and digits |
| Expiry | None. A key works until it is deleted. |
| Keys per project | Several can be active at once |
| Activation and revocation | Take effect within about a minute |
### Who can see a key
Only people with **Manage API keys** on a key's project (Editors and Admins) can see or copy its full value. Everyone else sees the last four characters. A key grants full API access, so holding it would bypass role limits in the dashboard.
### Rotating a key
1. Create a new key in the same project.
2. Move your integration to the new key and confirm requests succeed.
3. Delete the old key. Deletion cannot be undone.
Keep at least one key in every project you use. The dashboard also uses a project key for account and session pages, verification sessions, list changes, email submissions and webhook changes. Deleting a project's last key stops those features until a new key is created.
## Webhooks
A webhook sends a `POST` request to your endpoint when a chosen event occurs in a project. **Email Intel Completed** (`email.intelligence.completed`) fires when an email analysis finishes; see the [Webhook Payload Reference](/email-intelligence/webhook-reference).
Each webhook has its own signing secret, shown after creation and available in the table afterwards. Every delivery carries an `x-signature` header made with it. Verify it before trusting a payload, as described in [Webhook Signature Verification](/email-intelligence/webhook-signature-verification).
* A new webhook is enabled when created.
* While a webhook is disabled, events are not delivered, and they are not sent later.
* Creating a webhook needs an API key in the project.
The dashboard creates webhooks and turns them on and off. To change a URL or events, create a new webhook and disable the old one, or use the `webhooks_update` and `webhooks_delete` tools through the [MCP server](/dashboard/mcp-access).
## Permissions
| Action | Viewer | Analyst | Editor |
| - | - | - | - |
| See which API keys and webhooks exist | ✓ | ✓ | ✓ |
| See and copy API key values | | | ✓ |
| Create and delete API keys | | | ✓ |
| Create webhooks and turn them on or off | | | ✓ |
Admins have every permission. Permissions apply per project. See [Managing Users](/dashboard/managing-users).
Keys and webhooks belong to a project, so Sandbox and Production each need their own. A Sandbox key works against `https://api.sandbox.verisoul.ai`, and a Production key against `https://api.prod.verisoul.ai`.
## Need Help?
If you have any questions about API keys and webhooks, please contact our support team at [support@verisoul.ai](mailto:support@verisoul.ai).
# Custom Dashboards
Source: https://docs.verisoul.ai/dashboard/dashboards
Build charts and tables in plain English and keep them on dashboards that stay current
A custom dashboard is a grid of charts and tables, called widgets, built by describing them in plain English. A builder version of the [AI Agent](/dashboard/ai-agent) writes the query and draws the result. The query is saved with the widget, so the dashboard re-runs it for whatever date range is selected.
Dashboards belong to the project. Everyone who can view dashboards in the project sees all of them, and anyone with **Manage dashboards** can edit or delete any of them.
## Building a widget
**Add widget** opens the builder, with a chat on one side and a live preview on the other.
1. Describe what to measure and how to break it down, such as *weekly verification pass rate* or *top 10 device risk signals, as a table*. Leave out the time period: widgets follow the dashboard's date range.
2. Type `@` to reference a specific table or column. **Explore data** lists everything the builder can query, with column descriptions.
3. Refine it in the same chat until the preview is right, then choose **Save to dashboard**.
Charts and tables from an AI Agent chat can also be added with **Add to dashboard**, as long as they were built from a query that can re-run.
## What the builder can query
| Data | Contents |
| - | - |
| Accounts | Risk scores and current decision per account |
| Sessions | Device, network and location signals per session |
| Verifications | Face Match and ID Check results and risk flags |
| Rules | Rule definitions and every time a rule fired |
| ID Check records | Document verification sessions and identity links across accounts |
| Session signals | Detailed device and network signals, for the last 90 days |
Widgets can be line, area, bar, horizontal bar, histogram, pie, grouped bar, stacked bar or scatter charts, or tables. Each widget is one query with one y-axis. The builder builds widgets only; investigations, list changes and file exports belong in the [AI Agent](/dashboard/ai-agent).
## Behavior
* Widgets reload when the dashboard opens and when the date range changes.
* Widget dates are whole days in UTC.
* Tables return 200 rows by default, and up to 10,000 when you ask the builder for more. Charts read up to 10,000 rows.
* **Edit in builder** saves the result as a new widget and leaves the original in place.
* Layout, renames and resizes save for everyone who opens the dashboard.
Deleting a dashboard or removing a widget applies to everyone in the project and cannot be undone.
## Permissions
| Action | Viewer | Analyst | Editor |
| - | - | - | - |
| View dashboards | ✓ | ✓ | ✓ |
| Chat in the builder | ✓ | ✓ | ✓ |
| Create, edit and delete dashboards and widgets | | ✓ | ✓ |
Admins have every permission. Dashboards can also be managed through the [MCP server](/dashboard/mcp-access) within the same permissions. Dashboards in Sandbox and Production are separate, since they belong to different projects.
## Need Help?
If you have any questions about custom dashboards, please contact our support team at [support@verisoul.ai](mailto:support@verisoul.ai).
# Deleting Account Data
Source: https://docs.verisoul.ai/dashboard/delete-account
Permanently delete an account and its personal data from a project
Deleting an account removes its personal data from a project. It is typically used for GDPR and CCPA erasure requests.
Deletion cannot be undone.
## Where to delete
| Where | Accounts per operation |
| - | - |
| **Accounts** table, by selecting rows | Up to 100 |
| An account page, with **Delete Account** | 1 |
| An ID Check session linked to an account, with **Delete Account** | 1 |
| The [Delete Account API](/api-reference/account/delete) | 1 per call |
Each deletion is confirmed by typing `DELETE`.
## What deletion does
We delete all PII about the account, including images, videos, email, phone and name.
The account is also removed from every list, including **Allow**, **Block** and **Main Account**, and its links to other accounts are removed.
Deletion applies to one project in one environment. An account that exists in several projects, or in both Sandbox and Production, must be deleted in each.
The Accounts table and list pages reflect a deletion within a few minutes.
## If the account ID returns
If your integration later sends a session with the same account ID, Verisoul creates a new account with no history. It does not inherit the deleted account's list memberships, so an account deleted while on **Block** returns unblocked.
## Permissions
Deleting accounts needs **Delete account data**, held by Editors and Admins. An Admin can add it to a custom role, for example for the person who handles privacy requests. See [Managing Users](/dashboard/managing-users).
## Need Help?
If you have any questions about deleting account data, please contact our support team at [support@verisoul.ai](mailto:support@verisoul.ai).
# Email Intelligence
Source: https://docs.verisoul.ai/dashboard/email-intelligence
Submit emails for analysis from the dashboard and review the results
**Verifications → Email** holds every Email Intelligence result in the project, whether it was submitted from the dashboard or through the [Email Intelligence API](/email-intelligence/overview). For what the scores, decisions and signals mean, see [Email Signals & Scores](/signals-scores/email) and [Deliverability Risk](/email-intelligence/deliverability-risk).
| Tab | Contents |
| - | - |
| **Emails** | Every analyzed email, one row each |
| **Batches** | Each batch, with its progress and error count |
| **Analytics** | Daily volumes, with and without Identity Intelligence |
## Submitting emails
**Submit Emails** accepts:
| Method | Limit |
| - | - |
| **Single** | One email, with optional name, phone and country |
| **Paste** | Up to 100,000 emails, one per line |
| **Upload** | A CSV of up to 100,000 rows |
Paste and upload create a named batch. Batch names can use letters, digits, spaces, underscores and dashes.
Dashboard submissions go through the same API as your integration: they are billable, and a webhook subscribed to **Email Intel Completed** receives their results.
### CSV format
| Column | Required |
| - | - |
| `email` | Yes |
| `first_name`, `last_name` | No |
| `phone` | No. E.164 is recommended, such as `+15550109988`. |
| `country` | No. A two-letter code, such as `US`. |
Columns with these names are mapped automatically, and others can be mapped by hand. Rows without a valid email are skipped.
### Large lists
Lists over 1,000 emails upload in chunks of 1,000, all in the same batch. Keep the window open until the upload finishes; closing it stops the upload after the chunks already sent. Batches process at about 250 emails a minute per project, so 100,000 emails take about 7 hours. Single emails are processed ahead of batches.
## Identity Intelligence
**Run Identity Intelligence** adds research on the person behind each email: connected names and phone numbers, registered online accounts, and a match of the submitted name, phone and country against that data. It costs extra, and applies to every email in the submission. See [Identity Intelligence](/email-intelligence/identity-intelligence).
To add Identity Intelligence to an existing result, choose **Run Identity Intelligence** on the email's page, or **Run Email Intelligence** on an account page. Either creates a new request; the original result stays as it was.
## Results
An email's page shows its decision, send recommendation, risk and trust signals, email and domain details, breach history, Identity Intelligence results when available, and the accounts in the project that use the address.
New results take about 20 seconds to appear in the **Emails** table. The email's own page and its batch page show them as soon as they are ready.
## Permissions
| Action | Viewer | Analyst | Editor |
| - | - | - | - |
| View emails and batches | ✓ | ✓ | ✓ |
| Download emails | | ✓ | ✓ |
| Submit emails | | | ✓ |
Admins have every permission. **Submit emails for analysis** is marked billable in the role builder. The **Analytics** tab uses usage data, which needs **View usage** (Admin and Billing). See [Managing Users](/dashboard/managing-users).
## Need Help?
If you have any questions about Email Intelligence, please contact our support team at [support@verisoul.ai](mailto:support@verisoul.ai).
# Managing Lists
Source: https://docs.verisoul.ai/dashboard/manage-lists
Create lists and manage which accounts are on them from the dashboard
A list is a named set of accounts in a project. Every project has three standard lists, and you can create custom lists alongside them. How lists affect decisions, and how accounts move between the standard lists, is covered in [Lists](/action/lists).
| List | Name | Effect of membership |
| - | - | - |
| **Allow** | `allow` | Decision set to Real |
| **Block** | `block` | Decision set to Fake |
| **Main Account** | `main_account` | Multiple accounts score set to 0 |
| Custom lists | Your choice | None on the decision. Used for review, rules and reporting. |
## Creating a list
**Create New List** on the **Lists** page takes a name and a description. Names are normalized to lowercase, with spaces and hyphens turned into underscores and other characters removed, so *High Risk - Q3* becomes `high_risk_q3`. Rules, the API and your integration refer to the list by this name, and it cannot be changed later.
Lists are also created automatically when a rule action or an authenticate call's `lists` field names a list that does not exist.
## Adding and removing accounts
| From | How | Limit |
| - | - | - |
| A list's page | **Add Accounts to List**, then paste account IDs, one per line | 10,000 |
| The **Accounts** table | Select accounts, then **Add to List** or **Remove from List** | 10,000 |
| An account's page | **Add to List** or **Remove from List** | 1 |
| A verification session's **Matches** tab | **Add to List** on a linked account | 1 |
There is no file upload. To add from a CSV, paste its account ID column. Only accounts Verisoul has already scored can be added.
Changes can take a few seconds to appear in tables and counts.
### Standard list conflicts
An account can be on any number of custom lists, but on only one of **Allow**, **Block** and **Main Account**. Adding it to one of them moves it off the other when the [list hierarchy](/action/lists#list-hierarchy-and-movement) allows the move:
| Account is on | Can be added to | Held back from |
| - | - | - |
| **Block** | Allow, Main Account | |
| **Main Account** | Allow | Block |
| **Allow** | | Block, Main Account |
Accounts held back during a bulk add are listed in **List Membership Conflicts**. To move an account down the hierarchy, remove it from its current list first.
### Removing from a standard list
When an account is removed from **Allow**, **Block** or **Main Account**, Verisoul evaluates it again, and a rule that adds accounts to that list can put it back. Change the rule first to keep the account off.
## A list's page
A list's page shows its accounts with a **First Added to List** column, and can switch to the sessions of those accounts from the last 89 days. List entries record only when an account was added. The **Rules** column and the account's **Hit Rules** show which rules acted on it.
## Deleting a list
Custom lists can be deleted from their page. **Allow**, **Block** and **Main Account** are permanent.
Deleting a list cannot be undone, and it does not update your rules. A rule or authenticate call that adds to the deleted list creates it again, empty. Update rules and your integration first.
## Through the API
The [Lists API](/api-reference/lists/get) covers the same operations, plus changing a list's description (`PATCH /list/{list_name}`) and moving up to 1,000 accounts between lists in one call (`POST /list/{list_name}/account/move`). The [AI Agent](/dashboard/ai-agent) can also create lists and change their members.
## Permissions
| Action | Viewer | Analyst | Editor |
| - | - | - | - |
| View lists | ✓ | ✓ | ✓ |
| Download a list's accounts | | ✓ | ✓ |
| Create and delete custom lists, and change their members | | ✓ | ✓ |
| Change **Allow**, **Block** and **Main Account** members | | | ✓ |
Admins have every permission. **Allow**, **Block** and **Main Account** change the decision your integration receives, so they need **Change Allow and Block lists** in addition to **Manage custom lists**. See [Managing Users](/dashboard/managing-users).
Each project has its own lists, including its own standard lists, and Sandbox and Production projects are separate.
## Need Help?
If you have any questions about managing lists, please contact our support team at [support@verisoul.ai](mailto:support@verisoul.ai).
# Managing Users
Source: https://docs.verisoul.ai/dashboard/managing-users
Invite teammates, assign roles, and control project access in your Verisoul dashboard
Access in the Verisoul dashboard is set in two places. An **organization role** covers what someone can do to the account. A **project role** covers what they can do with the data inside each project.
Admins manage both from **Settings → Team**.
## Organization roles
Everyone in your organization has exactly one organization role.
| Role | What it covers | Project access |
| - | - | - |
| **Admin** | The account itself: creating and archiving projects, managing the team, viewing usage | Every project, automatically |
| **Member** | Work inside the projects they are granted; can see who is on the team | Only the projects granted to them |
| **Billing** | Usage and invoices; can see who is on the team | None |
Billing exists for the person who handles invoices and usage while staying out of fraud data.
A new Member starts with zero project access. Grant projects during the invite, or their dashboard will be empty when they sign in.
## Project roles
Project roles are granted per project. Someone can be an Editor on one project and a Viewer on another.
| Role | What they can do |
| - | - |
| **Viewer** | Read accounts, sessions, verifications, rules, lists, dashboards and analytics |
| **Analyst** | Everything a Viewer can do, plus exporting data, building dashboards, and creating custom lists |
| **Editor** | Everything an Analyst can do, plus editing and publishing rules, changing the Allow and Block lists, starting verifications, submitting emails for analysis, scheduling agent runs, and managing API keys, webhooks and custom hostnames |
The line between Analyst and Editor follows what changes live traffic. An Analyst can build any custom list they like. Putting an account on **Allow** or **Block** is an Editor action, because it changes the decision your integration receives. The same reasoning splits editing a rule and turning it on into two separate permissions, so a custom role can allow one and hold back the other.
### Custom roles
Admins can build their own project role from the same permission list. Start from Viewer, Analyst or Editor, then select the permissions the job needs. Custom roles sit alongside the built-in ones on the **Roles** tab and are assigned the same way.
The **Roles** tab lists every role, its scope, and the permissions it covers. Use it as the reference when deciding what to grant.
A role cannot be deleted while anyone still holds it, and a pending invitation naming it counts as well. **Move everyone holding this role** reassigns them in one step, which is usually what you want before deleting it. Deleting a role never removes access as a side effect of tidying up.
## Inviting a user
The **Members** tab lists everyone in the organization, their organization role, and the projects they reach. Pending invitations appear here as well, marked **Invited**.
Most people are Members. Choose Admin for someone who should manage projects and other people. Choose Billing for someone who needs usage and invoices only.
Select each project and set the role for it. **Select all** with the role picker beside it applies one role across the whole list at once, which is usually what a new teammate needs. Custom roles appear in the same picker.
Admin and Billing skip this step. Admins reach every project already, and Billing reaches none.
The user receives an email invitation. Until they accept, they appear in the list as **Invited** with the roles they will get. Their access is live as soon as they accept.
## Changing or removing access
**Edit access** reopens the same choices for an existing member. It replaces what they had, so clearing a project takes that project away from them.
Select several people first and the same control changes them together. Anything left unticked stays as each person has it. Anything you do tick is replaced wholesale: everyone selected ends up with exactly the project access you chose.
**Remove** takes the person out of the organization. To keep someone in the organization while cutting their access to data, set them to Member and clear every project.
Pending invitations can be withdrawn from the same list.
The Verisoul support account appears in your member list and cannot be removed. It is how we reach your account when you ask us for help.
## MCP connections hold roles too
An AI client reaches your data through [MCP access](/dashboard/mcp-access), either by a person signing in from the client or with a key. Both are bound by the same organization and project roles you set here.
A **sign-in** is the person. It holds their role live, so changing their access changes what the client can do within seconds, and removing them from the organization ends the connection.
A **key** holds a role exactly as a person does, so the same roles decide what it can do.
Two things follow from that:
* **A key is created from the access of whoever creates it**, and can only be narrower. Key creation therefore stays inside the limits you already set with roles.
* **A key is retired when its creator's access changes.** Reduce someone's projects, change their organization role, or remove them, and the keys they created stop working at the same moment.
Because a key holds a role, it can also be the reason a role will not delete. The count in that message includes tokens as well as people. Keys are listed under **Settings → MCP access**, where an Admin sees every key in the organization.
## Sandbox and Production are separate
Project access is granted per environment. A grant made in Production applies in Production only. A grant made in Sandbox applies in Sandbox only.
Organization roles carry across both environments, and so does membership: someone in your organization is in it in both places, with the same organization role.
Project grants work per environment, because Sandbox projects and Production projects are different projects holding different data. If you invite someone from Production and grant them two projects, they will find an empty dashboard in Sandbox until you switch environments and grant there as well.
## What only an Admin can do
* **Create, rename, reorder and archive projects.** An Editor works inside a project. Creating or retiring one is an Admin action.
* **Manage the team**: inviting people, changing roles, removing members. Everyone can open **Settings → Team** to see who is in the organization and what each role allows, but only an Admin can change anything there, and only an Admin sees pending invitations.
* **View usage**, which is also the one thing the Billing role provides on its own.
Within their granted projects, Editors can do everything else.
## Troubleshooting
Check the environment first. Confirm the grant was made in the same environment the user is signed into. If both match, refresh the page. Role changes take a few seconds to apply.
They have an organization role with zero project grants, which is expected for a new Member. Open **Edit access** and select the projects they should reach.
Hover it to see which role it requires. Controls stay visible so people can see what exists and ask for it. An Admin can change their project role, or build a custom role with that one permission.
An MCP key is holding it. Keys hold roles the same way people do, and they are listed under **Settings → MCP access**. A key cannot be moved to another role. If the client still needs access, create a replacement key, then revoke the old one and delete the role.
MCP keys are retired when the access of the person who created them is reduced or removed. If that integration needs to keep running, have someone who will keep the access create a new key for it.
A removed account comes back as a new user, so the previous roles stay behind. Grant their access again after they accept.
You are not an Admin. Everyone can see who is on the team and what each role allows; inviting, removing and changing roles are Admin actions. Ask an Admin to make the change.
Contact [support@verisoul.ai](mailto:support@verisoul.ai) and we will restore admin access to the account.
## Need Help?
If you have any questions about managing dashboard users, please contact our support team at [support@verisoul.ai](mailto:support@verisoul.ai).
# MCP Access
Source: https://docs.verisoul.ai/dashboard/mcp-access
Connect an MCP client to your Verisoul data, by signing in as yourself or with a key that carries your own permissions
The Verisoul MCP server connects an AI client to your Verisoul data. Claude, Cursor, VS Code, and anything else that speaks the [Model Context Protocol](https://modelcontextprotocol.io) can work with your accounts, sessions, rules, lists and dashboards directly.
There are two ways in, and both reach exactly what you reach in the dashboard:
| | Best for | How it identifies you |
| - | - | - |
| **Sign in from the client** | Interactive tools: Claude, Cursor, VS Code | Your own account. Each call is recorded as you. |
| **A key** | Anything that runs on its own: scripts, scheduled jobs, CI, unattended agents | The key. Each call is recorded as the key, with your name as its creator. |
The endpoint is `https://dashboard.verisoul.ai/api/mcp`. For Sandbox, use `https://dashboard.sandbox.verisoul.ai/api/mcp`.
## Signing in from your client
Nothing to create or paste. Point the client at the endpoint, and it opens a browser tab where you sign in to Verisoul and approve the connection. The approval screen names the client asking and, if you belong to more than one organization, asks which one this connection is for.
```bash theme={null}
claude mcp add --transport http verisoul https://dashboard.verisoul.ai/api/mcp
```
Then run `/mcp` inside Claude Code, choose **verisoul**, and sign in when the browser opens.
Open **Settings → Connectors → Add custom connector**, enter `https://dashboard.verisoul.ai/api/mcp`, and sign in when prompted. A connector added on the web appears in Claude Desktop as well; they share the account.
Open **Settings → Tools & MCP → Add** and give it the URL only, with no headers:
```json theme={null}
{
"mcpServers": {
"verisoul": { "url": "https://dashboard.verisoul.ai/api/mcp" }
}
}
```
Cursor shows **Needs login** next to the server; choose it to sign in.
Add the server to your `settings.json` and start it. VS Code opens a sign-in prompt the first time.
```json theme={null}
{
"mcp": {
"servers": {
"verisoul": {
"type": "http",
"url": "https://dashboard.verisoul.ai/api/mcp"
}
}
}
}
```
What the client gets is your access: the organization you chose, the projects you reach, and the permissions your role gives you there. Change your role and the connection follows within seconds. Leave the organization and it stops.
To work with a different organization, disconnect the server in the client and connect again. The approval screen asks which organization each time.
You can end a connection from the client at any time. Signing out of Verisoul does not affect it, and it lasts until you disconnect or an Admin removes you.
## Keys, for clients that run on their own
A key reaches exactly what you reach. It is created from your own access, so a client holding it can do what you can do in the dashboard, and that is its limit.
Keys are managed from **Settings → MCP access**. Any member can create one.
### Creating a key
The list shows every key you have created, the role it holds, the projects it reaches, and when it was last used. Admins see every key in the organization; everyone else sees their own.
The name appears in this list and in the audit trail. *Fraud ops agent* or *Nightly export script* will mean something to you in six months. *My key* will not.
30 days, 90 days, one year, or does not expire. An expired key stops working on its own, which is the simplest way to limit a credential you need for a while.
**The same as your access** gives the key everything you have today. This suits a client that works alongside you.
**Only certain projects** narrows it. Pick the projects and the role for each, which is useful when a script reads one project and you would rather keep the rest out of its hands.
In both cases the key stays inside your own access.
The secret is shown once, at creation, and cannot be retrieved afterwards. Copy it into your client's configuration straight away. A client that loses it needs a new key.
### Keep the key secret
**Never share an MCP key directly.** Do not paste it into a chat, a ticket, a shared document, a screenshot, or source code you commit.
A key is a live credential. It carries your access and needs no sign-in, so anyone holding it can read and change whatever you can, and the audit trail will show your key as the actor.
Give it to your MCP client through a secrets manager or an environment variable. If a key does get out, revoke it from this list and create another. Revoking takes effect immediately.
### Connecting a client with a key
Send the key as a bearer token against the MCP endpoint.
```json Desktop client theme={null}
{
"mcpServers": {
"verisoul": {
"type": "http",
"url": "https://dashboard.verisoul.ai/api/mcp",
"headers": { "Authorization": "Bearer ${VERISOUL_MCP_KEY}" }
}
}
}
```
```bash Command line theme={null}
npx @modelcontextprotocol/inspector@latest --cli \
https://dashboard.verisoul.ai/api/mcp \
--transport http \
--header "Authorization: Bearer $VERISOUL_MCP_KEY" \
--method tools/list
```
Keep the key in an environment variable, as shown above. Configuration files get synced between machines and committed to repositories.
## What a connection can reach
Two things decide what a call is allowed to do, and both have to agree:
| What decides it | Where it comes from |
| - | - |
| **Your role** | The organization and project roles you hold, exactly as in the dashboard |
| **The credential's limit** | For a sign-in, your own access. For a key, what you chose when you created it |
The limit can only narrow. Choosing *Only certain projects* on a key takes projects away from what it reaches, and everything offered there is already within your own access.
The client's first call should be `access_context_get`, which returns the permissions and the project IDs the connection reaches. Every project-scoped tool needs a project ID from that list. A project outside its reach comes back as not found, which is the same answer an unknown project ID gets, so a client has no way to enumerate the rest.
Around 88 tools exist in total. The server offers each client the ones its connection can use, so a client that sees fewer tools than a colleague's has a narrower key, a narrower role, or both.
## Limits
Some tools start work that runs for a while: analytics queries, agent runs, signed-URL query results, and exports. Each family has a ceiling on how many can be in flight at once, so one runaway client cannot crowd out the rest of your organization.
| Family | In flight per connection | In flight per organization | Per project |
| - | - | - | - |
| Analytics queries | 5 | 20 | 20 a day |
| Agent runs | 5 | 20 | 50 messages a day |
| Signed-URL query results | 1 | — | 5 a day |
| Exports | 5 | — | 10 an hour |
A connection is one person's sign-in or one key. A client that fans out across several projects gets five runs at once, not one.
When a ceiling is reached the tool answers `RUN_LIMIT_REACHED` with `retry_after_seconds`, and the right response is to wait and poll rather than resubmit. Short bursts of ordinary calls are bounded separately and answer `RATE_LIMITED`. Operations that spend money, such as email deep research and agent runs, also draw on a monthly quota for the organization.
Call `usage_get` at any time for the live ceilings, the quota that remains, and what is currently in flight.
## Every call is recorded
Each tool call and resource read is written to your organization's audit trail with who made it, which tool, which project, whether it was allowed, and how long it took. A sign-in is recorded as the person; a key is recorded as the key, under the name you gave it.
Your client can read the trail with the `audit_events_list` tool, which is the quickest way to answer what an agent actually did.
## Keys follow your access
A key holds a role the same way a person does, and it is tied to the access you had when you created it.
Reduce or remove your access and every key you created is retired at the same moment, and stops working immediately. That covers a change to your project grants, a change to your organization role, and leaving the organization.
For an integration that has to keep running through changes to your own access, have it created by someone who will keep that access. A shared service account is the usual answer for a long-lived integration.
## Managing keys
The list shows each key's state:
| State | What it means |
| - | - |
| **Active** | Working |
| **Expired** | Passed its expiry date and stopped working on its own |
| **Revoked** | Deliberately stopped |
Expired and revoked keys stay in the list, so it also answers what has ever been able to reach your account.
**Revoking** a key stops it at once and clears the roles behind it. It cannot be undone: a client that still needs access needs a new key. You can revoke your own keys, and an Admin can revoke anyone's.
Revoking is worth doing even for a key that already expired. It clears the roles the key still holds, which matters when you are trying to delete a role.
## Sandbox and Production are separate
A connection reaches one environment. A key is created in one environment and works only there, and a sign-in against the Sandbox endpoint reaches Sandbox data only.
This follows the same rule as project access: Sandbox projects and Production projects are different projects holding different data. If your client needs both, connect it to each endpoint separately.
## Troubleshooting
The connection reaches very little. Call `access_context_get` to see what it holds. The usual causes are a key narrowed to projects your access has since dropped, or an organization role with zero project grants behind it.
For a key, check it is still active in **Settings → MCP access**. A key is retired automatically when the access of the person who created it is reduced or removed, and it shows as revoked here.
For a sign-in, disconnect the server in the client and connect again. If sign-in completes but calls are still refused, you may no longer hold a role in that organization; ask an Admin.
The client is older than the current MCP authorization standard. Update it, or use a key instead.
The approval screen at sign-in asks which organization to use. Disconnect the server in the client and connect again to choose another.
The project sits outside what the connection can reach. Use a project ID from `access_context_get`.
That family has as many runs in flight as the ceiling allows, for you or for the organization. Wait `retry_after_seconds` and poll the run you already started rather than starting another. `usage_get` shows what is in flight.
Roles are still being switched on for your organization, so there is nothing yet to build a key from. Contact [support@verisoul.ai](mailto:support@verisoul.ai).
You hold an organization role with zero project grants. Ask an Admin to grant the projects you need, then create the key.
Revoke the key and create another. The secret is shown once at creation and cannot be retrieved.
That is intended. Revoked keys stay in the list as the record of what existed and what it could reach. They are inert.
## Need Help?
If you have any questions about MCP access, please contact our support team at [support@verisoul.ai](mailto:support@verisoul.ai).
# Dashboard Overview
Source: https://docs.verisoul.ai/dashboard/overview
What each part of the Verisoul dashboard is for
The Verisoul dashboard is where your team monitors fraud, investigates accounts, reviews verifications, and manages the rules and lists that decide what your integration receives.
Production is at [dashboard.verisoul.ai](https://dashboard.verisoul.ai) and Sandbox is at [dashboard.sandbox.verisoul.ai](https://dashboard.sandbox.verisoul.ai).
## Sections
| Section | What it covers | Guides |
| - | - | - |
| **Insights** | Trends, trust tiers, anomalies, and dashboards you build | [Overview and Analytics](/dashboard/analytics), [Custom Dashboards](/dashboard/dashboards) |
| **Investigate** | Accounts, sessions, and the signals behind each decision | [Accounts and Sessions](/dashboard/accounts-and-sessions), [Deleting Account Data](/dashboard/delete-account) |
| **Verifications** | Face Match, ID Check and Email Intelligence results | [Reviewing Verifications](/dashboard/verifications), [Email Intelligence](/dashboard/email-intelligence) |
| **Controls** | Lists and rules | [Managing Lists](/dashboard/manage-lists), [Building Rules](/dashboard/rules) |
| **AI Agent** | Questions, reports and list changes in plain English | [AI Agent](/dashboard/ai-agent), [MCP Access](/dashboard/mcp-access) |
| **Settings** | Team, keys, webhooks, projects and usage | [Managing Users](/dashboard/managing-users), [API Keys and Webhooks](/dashboard/api-keys-and-webhooks), [Projects and Usage](/dashboard/projects-and-usage) |
Face Match and ID Check appear for organizations that use verifications.
## Projects and environments
Everything in the dashboard belongs to a project. Sandbox and Production hold separate projects with separate data, so a rule, list, key or permission set in one environment applies only there.
## Roles
Your organization role and project roles decide what you can see and change. Most actions your role does not cover stay visible and disabled. See [Managing Users](/dashboard/managing-users).
# Projects and Usage
Source: https://docs.verisoul.ai/dashboard/projects-and-usage
Manage projects and their settings, and see your organization usage
Everything in the dashboard belongs to a project: accounts, sessions, rules, lists and settings. Projects are managed under **Settings → Projects**, and usage across projects under **Settings → Usage**.
## Projects
Admins create, rename, reorder and archive projects.
| Action | Effect |
| - | - |
| Create | Creates the project in both Sandbox and Production. The name's slug must be unique in the organization, including archived projects. |
| Rename | Changes the slug, which changes the project's dashboard URLs. The Project ID and API keys are unchanged. |
| Reorder | Sets the project order for everyone in the organization |
| Archive | Removes the project from the project menu and usage. Its data is deleted after 30 days. |
Archived projects cannot be restored from the dashboard. Contact [support@verisoul.ai](mailto:support@verisoul.ai) within 30 days to restore one.
Renaming, reordering and archiving apply only to the environment you are in. Sandbox and Production versions of a project have different Project IDs and hold different data.
## Project settings
Each project's **Project Settings** tab has two sections. Everyone with access to the project can view them. Changing them needs **Change SDK settings**, which by default only Admins hold.
### Multi Accounting Groups
Groups split the multi-accounting graph within a project, so accounts only link to accounts in the same group. Once enabled, every account must include a group, and the **Accounts** table gains a **Group** column. See [Multi Accounting Groups](/integration/advanced/multi-accounting-groups).
Multi Accounting Groups cannot be disabled once enabled.
### Apple Device Check
Connects the project to your Apple developer account so Verisoul can validate App Attest and DeviceCheck results from your iOS app. These appear as the `oem_app_integrity` and `oem_device_integrity` verdicts described in [Mobile](/signals-scores/mobile).
| Field | Value |
| - | - |
| **Team ID** | Your Apple Team ID |
| **App Attest** / **Bundle Identifier** | Enable App Attest and enter your app's bundle identifier |
| **Device Check** / **Private Key ID** / **Private Key (.p8 file)** | Enable DeviceCheck and enter a key created with DeviceCheck permission |
With DeviceCheck enabled, Verisoul validates the credentials with Apple before saving. See [iOS Device Check](/integration/frontend/ios#ios-device-check) for app setup.
## Usage
**Settings → Usage** shows organization-wide volumes for the current environment, by day or month, split by active project.
| Chart | Counts |
| - | - |
| **Accounts** | Accounts with at least one session, once per day or month |
| **Sessions** | Sessions |
| **ID Check** / **Face Match** | Verification sessions started, for organizations with verifications |
| **Email (Basic)** | Completed email analyses without Identity Intelligence |
| **Email (with Identity Intelligence)** | Completed email analyses with Identity Intelligence |
In the **Day** view, an account active on five days counts five times. Usage shows counts only, with no pricing. Archived projects are excluded.
Usage needs **View usage**, held by Admins and the Billing role.
## Permissions
| Action | Who |
| - | - |
| View projects and project settings | Every project role |
| Create, rename, reorder and archive projects | Admins |
| Change project settings | Admins, or a custom role with **Change SDK settings** |
| View usage | Admins and Billing |
See [Managing Users](/dashboard/managing-users).
## Need Help?
If you have any questions about projects or usage, please contact our support team at [support@verisoul.ai](mailto:support@verisoul.ai).
# Building Rules
Source: https://docs.verisoul.ai/dashboard/rules
Create, backtest and publish rules in the dashboard
A rule is evaluated on a trigger, checks its conditions against the account, session or verification, and adds the account to a list or removes it from one. This page covers building and managing rules in the dashboard. How rules are evaluated and how lists interact is covered in [Rules](/action/rules).
## Creating a rule
**Create New Rule** opens the builder, optionally from a template:
| Template | Sets up |
| - | - |
| **Blocked Countries** | Block accounts from chosen countries |
| **Blocked Domains** | Block accounts using listed email domains |
| **Allow Emails** | Allow specific email addresses |
| **Blocked ISPs** | Block accounts whose sessions come from listed service providers |
| **Allow ISPs** | Allow accounts whose sessions come from listed service providers |
A rule has a name (up to 25 characters, fixed after creation), a trigger, conditions, at least one action, and a status.
### Triggers
| Trigger | Evaluated |
| - | - |
| **Account Creation** | On the first session of each account |
| **All Sessions** | On every session |
| **ID Check Completed** | When an ID Check session linked to an account completes |
| **Face Match Completed** | When a Face Match session linked to an account completes |
Verification triggers offer verification fields only. Changing the trigger removes conditions the new trigger cannot use.
### Conditions
Conditions are rows of field, operator and value. Rows in a group must all match (AND), and groups are alternatives (OR). A rule can have up to 10 groups.
Any field in the Accounts and Sessions tables, and any field in the verification response, can be used. Notes on values:
* Percentages are entered from 0 to 100. Email Score ranges from -100 to 100, and IP Reputation Score from -1 (trusted) to 1 (risky).
* Value sets, such as countries, take up to 1,000 values.
* Fields with a fixed vocabulary, such as countries and decisions, accept only listed values.
* Comparisons such as **Greater Than** match only when the field has a value. Use **Blank** to match missing values.
* On the Lists field, **Equals** matches exact list membership.
### Actions
Actions add the account to a list or remove it from one, following the [list hierarchy](/action/lists#list-hierarchy-and-movement). An account on **Allow** is not moved to **Block** by an add action; add a **Remove from List** action for Allow first. Removals run before additions.
## Backtesting
**Rule Backtesting** runs the conditions against the project's current data while you build. It shows the matching accounts and sessions with their **Real**, **Suspicious** and **Fake** split.
| Aspect | Detail |
| - | - |
| Data | Accounts and sessions as they are now |
| Window | All accounts for account conditions, the last 89 days of sessions when a session field is used |
| Rows | Up to 1,000 shown, with CSV download |
Backtesting checks conditions only. It ignores the trigger, the actions, and current list membership, so it shows who matches the conditions today. A rule's statistics show who it acted on once it is live.
## Publishing
A rule runs while its status is **Enabled**. Turning a rule on or off needs **Turn rules on and off**, a separate permission from **Edit rules**, so a custom role can build and test rules without making them live. Without it, new rules save disabled, and editing a live rule keeps it live.
A rule can be saved to several projects at once when you create it. Each project is checked separately, and list actions refer to lists by name in each project.
## Versions and history
Every change writes a new version, including turning a rule on or off, archiving and restoring. **Rule Version History** keeps every version, and **Restore this version** copies an earlier one into a new version.
A rule's page shows how many accounts, sessions and verifications it hit in the selected period, and its trigger history (up to 50,000 rows per tab). Statistics count trigger events, so an account that triggers a rule on several sessions is counted each time.
**Duplicate** copies a rule into a new, disabled rule. **Archive** stops a rule and hides it from the default view; archived rules keep their history and their names, and rules are never deleted.
## Rules from the AI Agent
The [AI Agent](/dashboard/ai-agent) can draft a rule from your data. **Review in Rules** opens the builder with the suggestion and its backtest, and nothing is created until you save.
## Permissions
| Action | Viewer | Analyst | Editor |
| - | - | - | - |
| View rules, history and backtests | ✓ | ✓ | ✓ |
| Create, edit, duplicate, archive and restore | | | ✓ |
| Turn rules on and off | | | ✓ |
Admins have every permission. See [Managing Users](/dashboard/managing-users). Rules belong to a project, so Sandbox and Production rules are separate.
## Need Help?
If you have any questions about building rules, please contact our support team at [support@verisoul.ai](mailto:support@verisoul.ai).
# Reviewing Verifications
Source: https://docs.verisoul.ai/dashboard/verifications
Review Face Match and ID Check sessions, create sessions from the dashboard, and link sessions to accounts
Face Match and ID Check each have a page under **Verifications**, available to organizations that use verifications. Each page lists the project's sessions, opens any session for review, and charts completion. A session shows the same decision, risk score and risk flags your integration receives from `/verify-face` or `/verify-id`.
For how the products work and how to integrate them, see the [Verifications Overview](/verifications/overview). Risk flags are defined in the [Face Match](/verifications/face-match/resources/risk-flags) and [ID Check](/verifications/id-check/resources/risk-flags) references.
## Decisions follow your lists
A session linked to an account on **Block** shows **Fake**, and one on **Allow** shows **Real**, matching what the API returns. The risk score and risk flags always show the model's own result, and the **Lists** field shows which lists the account is on. See [Rules, Lists, and Verifications](/action/rules#rules-lists-and-verifications).
## Session tables
Each page lists completed sessions by default. **Show Incomplete Sessions**, in the table menu, adds sessions that stopped before the end, with a **Status** showing where they stopped, such as **Agreed To Terms** or **Liveness Failure**.
ID Check tables add the fields read from the document, such as ID country, type, number, name and date of birth.
## Reviewing a session
| Tab | Face Match | ID Check | Contents |
| - | - | - | - |
| **Identity** | | ✓ | Document data, user data, and document and user checks |
| **Devices & Network** | ✓ | ✓ | Network and device data, and network and device checks |
| **Photos** | ✓ | ✓ | The selfie and document scans |
| **Videos** | ✓ | ✓ | Session recordings |
| **Matches** | ✓ | ✓ | Accounts linked to this session by face, document number or device |
**Hit Rules** lists the rules the session triggered, through the **ID Check Completed** and **Face Match Completed** triggers. See [Building Rules](/dashboard/rules).
**Matches** draws up to 50 linked accounts. Linked accounts can be added to a list from the graph. See the multi-accounting sections of the [Face Match](/verifications/face-match/integration/response-signals#multi-accounting) and [ID Check](/verifications/id-check/integration/response-signals#multi-accounting) response signals.
Incomplete sessions show only the photos and videos captured before they ended. The other tabs need a completed session, and so do **Analyze with AI** and **Enroll Account**.
## Creating a session
**Create Session** creates a verification session and shows its link and QR code, for sharing with one person, such as a user your support team is checking manually. **Generate New Session Token** creates another session. Delivering the link is up to you.
Every verification session is billable, whether or not the person completes it. Opening **Create Session** creates a session immediately.
Sessions created this way have no account ID. Link the result with **Enroll Account**.
## Enrolling a session
**Enroll Account** links a completed session with no account to an account ID, with the same effect as passing `account_id` when the session is created. Until then, the session has no account and later sessions are not checked against it for repeats.
A session links to one account, within 30 days of creation. See [Enrolling an Account ID Later](/verifications/face-match/integration/initialize-session#enrolling-an-account-id-later).
## Usage
| Chart | Shows |
| - | - |
| **Completed Sessions per Day** | Completed sessions split into Fake, Suspicious and Real |
| **Completed Sessions per Country** | The 12 countries with the most completed sessions |
| **User Journey** | How many sessions reach each step, and where the rest drop out. Covers up to the last 60 days. |
## Permissions
| Action | Viewer | Analyst | Editor |
| - | - | - | - |
| View sessions and usage | ✓ | ✓ | ✓ |
| Download sessions | | ✓ | ✓ |
| **Create Session** and **Enroll Account** | | | ✓ |
| **Delete Account** | | | ✓ |
Admins have every permission. **Start verifications** is a separate permission because every session it creates is billable. **View verifications** includes the ID documents and selfies people uploaded. See [Managing Users](/dashboard/managing-users).
## Need Help?
If you have any questions about reviewing verifications, please contact our support team at [support@verisoul.ai](mailto:support@verisoul.ai).
# Deliverability Risk
Source: https://docs.verisoul.ai/email-intelligence/deliverability-risk
Understand deliverability signals and decide whether to send to an email address
Every email analysis returns deliverability signals that help you decide whether it is safe to send to an address. The webhook payload includes two fields for this purpose: `send_recommendation` and `deliverability.risk_score`. This guide explains what each field means and how to use them.
## Two Fields, One Decision
The `deliverability` section of the webhook payload contains:
```json theme={null}
{
"deliverability": {
"risk_score": -1,
"send_recommendation": "send"
}
}
```
* **`send_recommendation`** is the primary decisioning field. It combines deliverability with other risk factors (such as disposable domains) into a single, actionable recommendation.
* **`deliverability.risk_score`** is a narrower signal based on SMTP verification. Use it when you need more granular control over your send logic.
For most use cases, `send_recommendation` is all you need.
## send\_recommendation
| Value | Meaning | When it triggers |
| - | - | - |
| `send` | Safe to send | The email is confirmed deliverable or has no adverse deliverability signal |
| `send_with_caution` | Some bounce risk | The email has uncertain deliverability (unknown SMTP status, mailbox full, or risky status), or the overall `risk_score` is very high |
| `do_not_send` | Do not send | The email is confirmed invalid, is a no-reply address, or the domain is disposable or invalid |
`send_recommendation` accounts for more than just SMTP results. An email on a disposable domain will return `do_not_send` even if the mailbox technically exists, because disposable addresses are short-lived and poor targets for outreach.
## deliverability.risk\_score
The `deliverability.risk_score` ranges from **-1** (deliverable) to **1** (not deliverable). The table below shows roughly where scores land and what they indicate:
| Score range | Interpretation |
| - | - |
| Around **-1** | Confirmed deliverable — SMTP verification succeeded |
| Around **0** | Unable to verify — the mail server did not provide a definitive answer (e.g., catch-all domains, rate-limited servers) |
| Around **0.5** | Uncertain — SMTP status is unknown or the mailbox is full |
| Around **0.75** | Likely undeliverable — SMTP verification returned a risky status |
| Around **1** | Not deliverable — the email is confirmed invalid or is a no-reply address |
A score around 0 means Verisoul was **unable to verify** the address — it does not mean the email is unsafe. Many emails that cannot be SMTP-verified (for example, those on catch-all domains) deliver fine in practice.
## Which Should I Use?
Use `send_recommendation` as your only filter:
* **`send`** — safe to email
* **`send_with_caution`** — you can still send, but expect some bounce risk
* **`do_not_send`** — skip this address
This is the recommended approach for most customers.
Start with `send_recommendation`, then layer on `deliverability.risk_score` for tighter control:
1. Exclude anything with `send_recommendation = "do_not_send"`
2. Additionally exclude emails where `deliverability.risk_score > 0` to limit sending to addresses that are either confirmed deliverable or simply unverifiable
Keep in mind that filtering on `risk_score > 0` will exclude emails where SMTP verification was inconclusive. Many of those addresses are perfectly deliverable — this approach trades some reach for lower bounce rates.
Use the top-level `decision` and `risk_score` fields instead. Deliverability is already factored into the overall risk assessment, so you do not need to check `deliverability` separately.
## How Deliverability Affects the Overall Score
The `deliverability.risk_score` is one of several sub-scores that feed into the overall `risk_score` returned in the webhook. An undeliverable email will push the overall score toward risk, while a confirmed-deliverable email contributes a trust signal.
The overall `risk_score` also incorporates breach history, email age, domain reputation, username patterns, and (when enabled) identity intelligence. See the [Webhook Payload Reference](/email-intelligence/webhook-reference) for the full list of fields.
# Identity Intelligence
Source: https://docs.verisoul.ai/email-intelligence/identity-intelligence
Identity Intelligence extends the Email Intelligence API with deeper identity analysis. When enabled, Verisoul pulls in additional data sources to build a picture of the person behind an email address — their online presence, connected identities, and whether provided claims (name, phone, country) match the data.
This is particularly valuable for:
* **Account creation** — verify that the person signing up is real and matches the information they provided
* **High-value transactions** — add an extra layer of identity confidence before approving a purchase or transfer
* **KYC/onboarding workflows** — cross-reference claims against independent data sources
## How to Enable It
Set `identity_intelligence: true` in your request. In batch mode, this applies to all emails in the batch.
**Single email:**
```json theme={null}
{
"email": "john.doe@example.com",
"identity_intelligence": true,
"claims": {
"name": { "first": "John", "last": "Doe" },
"phone": "+15550109988",
"country": "US"
}
}
```
**Batch:**
```json theme={null}
{
"emails": [
{
"email": "john.doe@example.com",
"claims": {
"name": { "first": "John", "last": "Doe" },
"country": "US"
}
},
{
"email": "jane.smith@company.com",
"claims": {
"name": { "first": "Jane", "last": "Smith" },
"phone": "+14155551234"
}
}
],
"identity_intelligence": true
}
```
In batch mode, each email has its own `claims`, so you can verify different identities in a single request.
## Claims
Claims are pieces of information your customer has provided that you want to verify against Verisoul's identity data. All claims are optional.
| Claim | Type | Description |
| - | - | - |
| `name.first` | string | Customer's first name |
| `name.last` | string | Customer's last name |
| `phone` | string | Phone number (E.164 format recommended) |
| `country` | string | Two-letter country code (ISO 3166-1 alpha-2) |
When claims are provided, Verisoul matches them against identity data linked to the email and returns match results.
## What You Get Back
When `identity_intelligence` is enabled, the webhook payload includes an additional `identity_intelligence` section inside `data.result`:
```json theme={null}
{
"identity_intelligence": {
"match_risk_score": -0.4,
"online_accounts_count": 5,
"online_accounts": ["google", "linkedin", "github", "facebook", "instagram"],
"connected_names": ["John Doe"],
"connected_phones": ["+1555*******"],
"claim_matches": {
"name": "match",
"phone": "skipped",
"country": "match"
}
}
}
```
### Fields
| Field | Type | Description |
| - | - | - |
| `match_risk_score` | number | Composite identity risk score from -1 (verified identity) to 1 (ghost / no identity footprint). Factors in online presence, claim matches, and identity consistency. |
| `online_accounts_count` | number | Number of platforms where the email is registered. |
| `online_accounts` | string\[] | Platforms where the email is registered (e.g., `google`, `facebook`, `linkedin`, `twitter`, `github`). A rich online presence is a strong trust signal. |
| `connected_names` | string\[] | Names associated with this email across data sources. |
| `connected_phones` | string\[] | Phone numbers linked to this email. |
| `claim_matches` | object | Results of matching each submitted claim against identity data. |
### Claim Match Values
Each claim is matched independently and returns one of:
| Value | Meaning |
| - | - |
| `match` | The claim matches identity data linked to the email |
| `partial_match` | Partial overlap (e.g., first name matches but last name differs) |
| `no_match` | The claim does not match any known identity data |
| `skipped` | No claim was provided for this field, or identity data was unavailable |
## How It Affects the Score
When Identity Intelligence is enabled, the identity data directly influences the overall `risk_score`:
* **Identity Match** — a strong name/phone match pushes the score toward trust, while mismatches or ghost identities push toward risk.
* **Online presence** — emails registered on many legitimate platforms (Google, LinkedIn, Facebook, etc.) are more likely to belong to real people, which is a strong trust signal.
A verified identity match can pull an otherwise neutral email into the "trusted" range, while a complete identity mismatch can significantly increase risk.
## Without Identity Intelligence
If `identity_intelligence` is not set or is `false`, the webhook payload will not include the `identity_intelligence` section. Identity-related signals will not factor into the overall risk score.
The Email Intelligence API still provides full risk scoring, deliverability, domain classification, and breach intelligence without Identity Intelligence enabled.
# Integration
Source: https://docs.verisoul.ai/email-intelligence/integration
How to integrate the Email Intelligence API: configure webhooks, submit emails, and process results
## Prerequisites
Before you begin, make sure you have:
* A **Verisoul API key** (available from the [Dashboard](https://dashboard.verisoul.ai))
***
## Step 1: Configure Your Webhook Endpoint (Optional)
Webhook configuration is **optional**. All email intelligence results are available in the [Dashboard](https://dashboard.verisoul.ai) regardless of whether a webhook is configured. Set up a webhook only if you need results delivered to your own endpoint in real time.
To configure a webhook, go to **Settings > Webhooks** in the Dashboard and add a new endpoint. Select the **Email Intel Completed** event type, enter your target URL, and click **Create**.
Your webhook endpoint must:
* Accept **POST** requests with **JSON** payloads
* Respond with a **2xx** HTTP status code on success
* Be **idempotent** (Verisoul may retry delivery)
* **Verify webhook signatures** for security (see [Step 3](#step-3-receive-and-verify-webhook-results))
Once configured, Verisoul will automatically deliver results to this endpoint when email analysis completes.
***
## Step 2: Submit Emails
Submit emails to the `POST /email` endpoint. You can submit a single email or a batch of emails.
Submit a single email with optional identity claims. Returns a `request_id` for webhook correlation.
Submit up to 1,000 emails per request with per-email claims. Returns a `batch_id` for grouping webhook results and for growing the batch across requests.
The API responds immediately with a `request_id` (single) or `batch_id` (batch), plus a `webhook_configured` boolean indicating whether a webhook subscription is active for your project. This confirms the email has been accepted for processing — it does not contain the analysis results.
### Building a batch across requests
Each batch request carries at most 1,000 emails. To run a larger list as one batch, send the first request as usual, then pass the returned `batch_id` in each request that follows. Those emails join the same batch, and each response reports the cumulative `total_count` and the batch's current `status`. A batch holds up to 100,000 emails.
```json theme={null}
{
"batch_id": "bbc73b22-bd47-48e4-b173-c9ca085efb2a",
"emails": [
{ "email": "third.person@example.com" },
{ "email": "fourth.person@example.com", "claims": { "country": "US" } }
]
}
```
A batch accepts new emails for 24 hours after its most recent request. After that window, a request carrying its `batch_id` returns a `400`, as does a `batch_id` from another project or a request that would take the batch past 100,000 emails. `batch_name` comes from the first request and is ignored afterwards. Each email has 24 hours to complete from the request that submitted it; any still pending after that come back with `error.code: "timed_out"`.
***
## Step 3: Receive and Verify Webhook Results
Once analysis completes, Verisoul delivers a webhook to your configured endpoint with the event type `email.intelligence.completed`.
### Webhook Signature Verification
All webhook requests include an `x-signature` header containing an HMAC-SHA256 signature over the timestamp, selected headers, and the raw request body.
See the **[Webhook Signature Verification](/email-intelligence/webhook-signature-verification)** guide for the full algorithm, code examples in Node.js, Python, Go, and C#, and troubleshooting tips.
Always verify the signature before processing the payload. Use the **raw request body** (not a parsed/re-serialized version) and enforce a timestamp tolerance of ±5 minutes.
### Processing the Payload
1. Verify the webhook signature
2. Check the `status` field — either `success` or `error`
3. On success, use `data.result.decision` and `data.result.risk_score` for your business logic
4. On error, inspect the `error` object for details
See the [Webhook Payload Reference](/email-intelligence/webhook-reference) for the full payload structure and field definitions.
### Latency and Delivery Guarantees
The time between submitting an email and receiving the webhook depends on whether Verisoul has previously analyzed the email or its domain.
**Single email requests:**
| Scenario | Typical latency |
| - | - |
| Invalid or previously analyzed email | Under **1 second** |
| New email on a known domain | Approximately **5 seconds** |
| New domain never seen before | **30–60 seconds** (one-time domain analysis) |
Verisoul guarantees that the webhook for a single email request will be delivered within **60 seconds** of submission. If analysis has not completed by then, a webhook is sent with `status: "error"` and `error.code: "timed_out"` so your system is never left waiting.
**Batch requests:**
Batch submissions are processed at a default throughput of approximately **250 emails per minute** per project. A batch of 1,000 emails is fully processed and all of its webhooks delivered within about **4 minutes** of submission, and a batch of 100,000 takes roughly **7 hours**.
Each email in the batch triggers its own webhook as soon as its analysis completes — you do not need to wait for the entire batch to finish before receiving results, and you can submit additional batches without waiting for earlier ones to drain.
If your use case requires higher throughput (for example, processing tens of thousands of emails in tight windows), [contact us](mailto:support@verisoul.ai) and we can raise your project's batch processing rate.
Batch emails are processed at a lower priority than single email requests, so real-time single-email submissions are never delayed by batch processing.
***
## FAQ
If your endpoint does not return a 2xx response, Verisoul retries delivery up to **3 times** over approximately **3.5 minutes**:
* 1st retry: 3 seconds after the initial failure
* 2nd retry: 10 seconds later
* 3rd retry: 3 minutes later
After the retry window expires, the webhook is marked as failed and no further attempts are made. Webhooks may be delivered more than once, so your endpoint must be idempotent.
Calls to `/email` count toward your API key's rate limit. If you need a higher limit, contact Verisoul.
All Verisoul webhooks are sent from:
```
34.71.198.163
```
You can allowlist this IP if your endpoint is behind a firewall. You should still verify webhook signatures regardless.
# Overview
Source: https://docs.verisoul.ai/email-intelligence/overview
The Email Intelligence API lets you submit one or more email addresses and receive a comprehensive risk assessment. Results are delivered via webhook and are always available in the [Dashboard](https://dashboard.verisoul.ai). It is designed for offline, asynchronous use cases where you need deep analysis before making a decision about an email.
Get a single risk score from **-1** (high trust) to **1** (high risk) along with a decision label.
Know if an email is deliverable before you send, with a clear recommendation: **send**, **send\_with\_caution**, or **do\_not\_send**.
See how many data breaches an email appears in, which breaches are from trusted sources, and the full breach history.
Optionally verify the person behind an email — discover online presence, match provided name and phone claims, and surface connected identities.
Automatically classify domains as personal, business, education, government, disposable, relay, or not active.
Analyze email characteristics including age, username patterns (likely generated, no-reply, role accounts), and online history.
## Identity Intelligence
For customers doing account creation, onboarding, or high-value transactions, The Email Intelligence API offers an optional **Identity Intelligence** layer that goes beyond email-level signals.
When enabled, Verisoul performs deeper identity analysis:
* **Online Presence** — discover which platforms an email is registered on (Google, Facebook, LinkedIn, etc.)
* **Name & Phone Verification** — match provided claims (name, phone, country) against identity data linked to the email
* **Connected Identities** — surface names, phone numbers, and countries associated with the email across data sources
See the full Identity Intelligence guide for details on claims matching, returned fields, and batch usage.
## How It Differs from Real-Time Email Scoring
Verisoul's `/session/authenticate` endpoint provides email risk context as part of a real-time session and account. The Email Intelligence API provides richer signals and is purpose-built for standalone email decisions.
| | Real-Time (Authenticate) | Email Intelligence API |
| - | - | - |
| **Use case** | Inline session scoring | Standalone email analysis |
| **Delivery** | Synchronous response | Asynchronous webhook |
| **Identity Intelligence** | Not available | Optional deep identity analysis |
| **Claims matching** | Not available | Per-email name, phone, country verification |
| **Batch support** | No | Up to 100,000 emails per batch, sent in requests of up to 1,000 |
## Get Started
Step-by-step instructions for configuring your webhook, submitting emails, and processing results.
# Webhook Payload Reference
Source: https://docs.verisoul.ai/email-intelligence/webhook-reference
Complete reference for the email.intelligence.completed webhook payload
## Payload Structure
When email analysis completes, Verisoul sends a `POST` request to your configured webhook endpoint.
For a full reference of all email risk signals, trust signals, and domain types, see [Email Signals & Scores](/signals-scores/email).
***
## Example Success Payload
```json theme={null}
{
"request_id": "d69b8d6a-06ae-4071-b6a7-f9abe000b73b",
"batch_id": null,
"project_id": "ed8fe5d7-ead9-4b81-b1b0-74190582341c",
"event_type": "email.intelligence.completed",
"created_at": "2026-03-09T18:22:10Z",
"completed_at": "2026-03-09T18:22:15Z",
"status": "success",
"data": {
"input": {
"email": "john.doe@test.verisoul.com",
"claims": {
"name": { "first": "John", "last": "Doe" },
"country": "US"
}
},
"result": {
"decision": "trusted",
"risk_score": -0.4514,
"trust_signals": [
"email_age_greater_than_5_years",
"email_known_online_history",
"strong_online_presence",
"identity_name_match"
],
"risk_signals": [],
"domain": {
"risk_score": -0.72,
"fqdn": "test.verisoul.com",
"apex": "verisoul.com",
"type": "business",
"age_years": 4.2,
"country": "US"
},
"email": {
"username": "john.doe",
"normalized": "john.doe@test.verisoul.com",
"first_seen_at": "2022-06-15",
"age_years": 3.8,
"seen_countries": ["US"]
},
"deliverability": {
"risk_score": -1,
"send_recommendation": "send"
},
"breach_intelligence": {
"breach_count": 1,
"trusted_breach_count": 0,
"breach_list": [
{
"source": "Adobe",
"breach_date": "2013-10-04",
"trust_weight": "moderate",
"country": "global",
"industry": "software"
}
]
},
"identity_intelligence": {
"match_risk_score": -0.4,
"online_accounts_count": 3,
"online_accounts": ["google", "linkedin", "github"],
"connected_names": ["John Doe"],
"connected_phones": ["+1555*******"],
"claim_matches": {
"name": "match",
"phone": "skipped",
"country": "match"
}
}
}
},
"error": null
}
```
The `identity_intelligence` section inside `data.result` is only present when the request was submitted with `identity_intelligence: true`.
***
## Error Payload
```json theme={null}
{
"request_id": "5fee305e-4b6e-41c4-bff9-c9b53e175d70",
"batch_id": null,
"project_id": "ed8fe5d7-ead9-4b81-b1b0-74190582341c",
"event_type": "email.intelligence.completed",
"created_at": "2026-03-09T18:22:10Z",
"completed_at": "2026-03-09T18:22:15Z",
"status": "error",
"data": null,
"error": {
"email": "some.email@random.com",
"code": "timed_out",
"message": "Unable to compute score of email"
}
}
```
***
## Field Reference
### Top-Level Fields
| Field | Type | Description |
| - | - | - |
| `request_id` | string | Unique identifier for this email intelligence request |
| `batch_id` | string \| null | Batch identifier, if the email was submitted as part of a batch |
| `project_id` | string | Your Verisoul project ID |
| `event_type` | string | Always `email.intelligence.completed` |
| `created_at` | string | When analysis started (ISO 8601) |
| `completed_at` | string | When analysis completed (ISO 8601) |
| `status` | string | `success` or `error` |
| `data` | object \| null | Analysis results (present on success) |
| `error` | object \| null | Error details (present on failure) |
### `data.input`
Echo of the submitted request, useful for correlating results.
| Field | Type | Description |
| - | - | - |
| `email` | string | The email address that was analyzed |
| `claims` | object | Claims submitted with the request (if any) |
### `data.result`
| Field | Type | Description |
| - | - | - |
| `decision` | string | Risk decision: `high_trust`, `trusted`, `moderate`, `risky`, or `high_risk` |
| `risk_score` | number | Overall risk score from -1 (high trust) to 1 (high risk) |
| `trust_signals` | string\[] | List of detected trust signals |
| `risk_signals` | string\[] | List of detected risk signals |
#### Decision Buckets
Every email receives a `decision` based on the overall `risk_score`:
| Decision | Score Range | Meaning |
| - | - | - |
| **high\_trust** | below -0.50 | Strong trust signals across multiple dimensions |
| **trusted** | -0.50 to 0.00 | Positive trust indicators present |
| **moderate** | 0.00 to 0.25 | Neutral — no strong signals in either direction |
| **risky** | 0.25 to 0.75 | Some risk indicators detected |
| **high\_risk** | above 0.75 | Strong risk signals — likely fraudulent or invalid |
### `data.result.domain`
| Field | Type | Description |
| - | - | - |
| `risk_score` | number | Domain risk score from -1 to 1 |
| `fqdn` | string | Fully qualified domain name |
| `apex` | string | Root/apex domain |
| `type` | string | Domain classification (see [domain types](/signals-scores/email#domain-types)) |
| `age_years` | number | Domain age in years |
| `country` | string | Country associated with the domain |
### `data.result.email`
| Field | Type | Description |
| - | - | - |
| `username` | string | Local part of the email address |
| `normalized` | string | Canonicalized email address |
| `first_seen_at` | string | Earliest known date this email appeared in data sources |
| `age_years` | number | Estimated age of the email address in years |
| `seen_countries` | string\[] | Countries where this email has been observed |
### `data.result.deliverability`
| Field | Type | Description |
| - | - | - |
| `risk_score` | number | Deliverability risk: -1 (deliverable) to 1 (not deliverable) |
| `send_recommendation` | string | `send`, `send_with_caution`, or `do_not_send` |
For guidance on how to use these fields to make send decisions, see [Deliverability Risk](/email-intelligence/deliverability-risk).
### `data.result.breach_intelligence`
| Field | Type | Description |
| - | - | - |
| `breach_count` | number | Total number of data breaches this email appears in |
| `trusted_breach_count` | number | Number of breaches from verified/trusted sources |
| `breach_list` | object\[] | Detailed list of breaches (name, date, data types exposed) |
### `data.result.identity_intelligence`
Only present when `identity_intelligence: true` was set in the request.
| Field | Type | Description |
| - | - | - |
| `match_risk_score` | number | Identity match risk from -1 (verified match) to 1 (no match / ghost identity) |
| `online_accounts_count` | number | Number of platforms where the email is registered |
| `online_accounts` | string\[] | Platforms where the email is registered (e.g., google, facebook, linkedin) |
| `connected_names` | string\[] | Names associated with this email across data sources |
| `connected_phones` | string\[] | Phone numbers linked to this email |
| `claim_matches` | object | Results of matching submitted claims against identity data |
### `data.result.identity_intelligence.claim_matches`
| Field | Type | Description |
| - | - | - |
| `name` | string | `match`, `partial_match`, `no_match`, or `skipped` |
| `phone` | string | `match`, `partial_match`, `no_match`, or `skipped` |
| `country` | string | `match`, `partial_match`, `no_match`, or `skipped` |
### `error`
| Field | Type | Description |
| - | - | - |
| `email` | string | The email address that failed during analysis |
| `code` | string | Error code (e.g., `internal_server_error`) |
| `message` | string | Human-readable description of the failure |
***
## Signal and Domain Type Definitions
For detailed definitions of all signals and domain classifications, see:
* [Domain Types](/signals-scores/email#domain-types)
* [Risk Signals](/signals-scores/email#risk-signals)
* [Trust Signals](/signals-scores/email#trust-signals)
# Webhook Signature Verification
Source: https://docs.verisoul.ai/email-intelligence/webhook-signature-verification
How to verify the HMAC-SHA256 signature on Verisoul webhook deliveries
Every webhook Verisoul delivers includes an `x-signature` HTTP header. You **must** verify this signature before processing the payload to confirm it was sent by Verisoul and has not been tampered with.
***
## Signature Header Format
The `x-signature` header contains three comma-separated components:
```
x-signature: t=1773933769,h=content-type x-event-id x-event-type,v1=388bce5537bf39d624211b1f5c1d19e0003b8c4b0513e38ac6aae381df7f5f71
```
| Component | Description |
| - | - |
| `t` | Unix timestamp (seconds) when the webhook was signed |
| `h` | Header names included in the signature: `content-type x-event-id x-event-type` |
| `v1` | Hex-encoded HMAC-SHA256 signature |
***
## How the Signature is Computed
The signature is an HMAC-SHA256 hash over a **signed data string** constructed by joining these values with `.` (dots):
```
signed_data = "{t}.{h}.{content-type}.{x-event-id}.{x-event-type}.{raw_body}"
```
For example, given:
* `t=1773933769`
* `h=content-type x-event-id x-event-type`
* `content-type: application/json`
* `x-event-id: 5ded1748-8c2f-4ef4-8276-32af793f62b0`
* `x-event-type: email.intelligence.completed`
* Body: `{"request_id":"ffd7df51-...","status":"error",...}`
The signed data string is:
```
1773933769.content-type x-event-id x-event-type.application/json.5ded1748-8c2f-4ef4-8276-32af793f62b0.email.intelligence.completed.{"request_id":"ffd7df51-...","status":"error",...}
```
The signature is then:
```
v1 = hex( HMAC-SHA256( secret, signed_data ) )
```
***
## Verification Steps
Split the `x-signature` header value on `,` and extract the `t`, `h`, and `v1` components. Reject the request if any component is missing.
Parse `t` as a Unix timestamp and compare it to your server's current time. Reject the request if the difference exceeds your tolerance (recommended: **5 minutes**). This prevents replay attacks.
Look up the value of each header named in `h` (`content-type`, `x-event-id`, `x-event-type`), then concatenate with `.` separators:
`{t}.{h}.{content-type value}.{x-event-id value}.{x-event-type value}.{raw_body}`
Compute `HMAC-SHA256` over the signed data string using your webhook subscription's **shared secret** as the key. Hex-encode the result (lowercase).
Compare your computed signature with the `v1` value using a **constant-time comparison** function to prevent timing attacks. Return `401 Unauthorized` if they do not match.
Always verify against the **raw request body** exactly as received — not a parsed and re-serialized version. JSON key ordering and whitespace matter for signature verification.
***
## Code Examples
```javascript Node.js theme={null}
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use('/webhook', express.json({
verify: (req, res, buf) => { req.rawBody = buf; }
}));
function verifySignature(rawBody, signature, headers, secret, toleranceSec = 300) {
const parts = Object.fromEntries(
signature.split(',').map(p => {
const i = p.indexOf('=');
return [p.slice(0, i), p.slice(i + 1)];
})
);
if (!parts.t || !parts.h || !parts.v1) return false;
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(parts.t)) > toleranceSec) return false;
const headerNames = parts.h.split(' ');
const headerValues = headerNames.map(h => headers[h] || '').join('.');
const signedData = `${parts.t}.${parts.h}.${headerValues}.${rawBody}`;
const expected = crypto.createHmac('sha256', secret).update(signedData).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
app.post('/webhook', (req, res) => {
const signature = req.headers['x-signature'];
const rawBody = req.rawBody.toString('utf8');
if (!signature || !verifySignature(rawBody, signature, req.headers, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
console.log('Verified webhook:', req.body.event_type);
res.json({ status: 'verified' });
});
app.listen(3000);
```
```python Python theme={null}
import hmac
import hashlib
import time
import os
from flask import Flask, request, jsonify
app = Flask(__name__)
def verify_signature(raw_body: bytes, signature: str, headers: dict, secret: str, tolerance_sec: int = 300) -> bool:
parts = {}
for segment in signature.split(','):
idx = segment.index('=')
parts[segment[:idx]] = segment[idx + 1:]
if 't' not in parts or 'h' not in parts or 'v1' not in parts:
return False
now = int(time.time())
if abs(now - int(parts['t'])) > tolerance_sec:
return False
header_names = parts['h'].split(' ')
header_values = '.'.join(headers.get(h, '') for h in header_names)
signed_data = f"{parts['t']}.{parts['h']}.{header_values}.{raw_body.decode()}"
expected = hmac.new(secret.encode(), signed_data.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts['v1'])
@app.route('/webhook', methods=['POST'])
def handle_webhook():
signature = request.headers.get('x-signature')
if not signature or not verify_signature(
request.data, signature, request.headers, os.environ['WEBHOOK_SECRET']
):
return jsonify({'error': 'Invalid signature'}), 401
payload = request.get_json()
print(f"Verified webhook: {payload['event_type']}")
return jsonify({'status': 'verified'})
if __name__ == '__main__':
app.run(port=3000)
```
```go Go theme={null}
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func verifySignature(body []byte, signature, secret string, headers http.Header, toleranceSec int64) bool {
parts := make(map[string]string)
for _, seg := range strings.Split(signature, ",") {
idx := strings.Index(seg, "=")
if idx < 0 {
continue
}
parts[seg[:idx]] = seg[idx+1:]
}
t, ok := parts["t"]
h, ok2 := parts["h"]
v1, ok3 := parts["v1"]
if !ok || !ok2 || !ok3 {
return false
}
ts, err := strconv.ParseInt(t, 10, 64)
if err != nil || int64(math.Abs(float64(time.Now().Unix()-ts))) > toleranceSec {
return false
}
names := strings.Split(h, " ")
vals := make([]string, len(names))
for i, name := range names {
vals[i] = headers.Get(name)
}
signedData := fmt.Sprintf("%s.%s.%s.%s", t, h, strings.Join(vals, "."), body)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(signedData))
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(v1))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
signature := r.Header.Get("x-signature")
if !verifySignature(body, signature, os.Getenv("WEBHOOK_SECRET"), r.Header, 300) {
http.Error(w, `{"error":"Invalid signature"}`, http.StatusUnauthorized)
return
}
var payload map[string]interface{}
json.Unmarshal(body, &payload)
fmt.Printf("Verified webhook: %v\n", payload["event_type"])
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "verified"})
}
func main() {
http.HandleFunc("/webhook", webhookHandler)
http.ListenAndServe(":3000", nil)
}
```
```csharp C# theme={null}
using System.Security.Cryptography;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var secret = Environment.GetEnvironmentVariable("WEBHOOK_SECRET")!;
app.MapPost("/webhook", async (HttpContext ctx) =>
{
ctx.Request.EnableBuffering();
using var reader = new StreamReader(ctx.Request.Body, Encoding.UTF8, leaveOpen: true);
var rawBody = await reader.ReadToEndAsync();
var signature = ctx.Request.Headers["x-signature"].FirstOrDefault();
if (signature == null)
return Results.Json(new { error = "Missing signature" }, statusCode: 401);
// Parse t, h, v1 from the signature header
string? t = null, h = null, v1 = null;
foreach (var seg in signature.Split(','))
{
var eq = seg.IndexOf('=');
if (eq < 0) continue;
var key = seg[..eq];
var val = seg[(eq + 1)..];
switch (key) { case "t": t = val; break; case "h": h = val; break; case "v1": v1 = val; break; }
}
if (t == null || h == null || v1 == null)
return Results.Json(new { error = "Invalid signature" }, statusCode: 401);
// Check timestamp tolerance (5 minutes)
if (!long.TryParse(t, out var ts) || Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - ts) > 300)
return Results.Json(new { error = "Timestamp expired" }, statusCode: 401);
// Build signed data string
var names = h.Split(' ');
var headerVals = string.Join(".", names.Select(n => ctx.Request.Headers[n].ToString()));
var signedData = $"{t}.{h}.{headerVals}.{rawBody}";
// Compute and compare HMAC
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var expected = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(signedData))).ToLowerInvariant();
if (!CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(v1)))
return Results.Json(new { error = "Signature mismatch" }, statusCode: 401);
return Results.Json(new { status = "verified" });
});
app.Run();
```
***
## Test Your Implementation
You can generate a valid signature locally and send a test webhook with `curl`:
```bash theme={null}
SECRET="your-webhook-secret"
BODY='{"event_type":"email.intelligence.completed","status":"success"}'
TIMESTAMP=$(date +%s)
EVENT_ID="test-event-123"
EVENT_TYPE="email.intelligence.completed"
SIGNED_DATA="${TIMESTAMP}.content-type x-event-id x-event-type.application/json.${EVENT_ID}.${EVENT_TYPE}.${BODY}"
V1=$(echo -n "$SIGNED_DATA" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
SIGNATURE="t=${TIMESTAMP},h=content-type x-event-id x-event-type,v1=${V1}"
curl -X POST http://localhost:3000/webhook \
-H "Content-Type: application/json" \
-H "x-event-id: ${EVENT_ID}" \
-H "x-event-type: ${EVENT_TYPE}" \
-H "x-signature: ${SIGNATURE}" \
-d "${BODY}"
```
***
## Troubleshooting
The most common cause is using a **parsed and re-serialized** request body instead of the raw bytes. JSON serializers may reorder keys, change whitespace, or alter number formatting. Always verify against the raw body exactly as received.
Ensure your server's clock is synchronized via NTP. The default tolerance is **5 minutes** (300 seconds). If your server clock drifts beyond this, verification will fail even with a valid HMAC. You can increase the tolerance, but keep it as short as practical.
Each webhook subscription has its own secret. You can find it in the Verisoul dashboard under your webhook subscription settings. Make sure you are using the secret that corresponds to the subscription receiving the webhook.
The header names listed in the `h` component (`content-type`, `x-event-id`, `x-event-type`) are **lowercase**. When looking up header values, make sure your framework's header access is case-insensitive or that you normalize to lowercase.
Verisoul uses **HMAC-SHA256**, not HMAC-SHA1. Double-check the hash algorithm in your implementation.
# Android Sample App
Source: https://docs.verisoul.ai/examples/android-sample-app
Complete reference implementation demonstrating Verisoul integration in an
Android application. Follow the README in the GitHub repository to get
started.
# Flutter Sample App
Source: https://docs.verisoul.ai/examples/flutter-sample-app
Complete reference implementation demonstrating Verisoul integration in a Flutter application. Follow the README in the GitHub repository to get started.
# iOS Sample App
Source: https://docs.verisoul.ai/examples/ios-sample-app
Complete reference implementation demonstrating Verisoul integration in an iOS application. Follow the README in the GitHub repository to get started.
# Face Match & ID Check Sample App
Source: https://docs.verisoul.ai/examples/react-native-facematch-sample-app
Complete reference implementation demonstrating Verisoul integration in a React Native application
This guide will help you set up and run the React Native Face Match & ID Check Sample App, which demonstrates how to integrate Verisoul into a React Native application.
## About the Sample App
The [React Native Face Match & ID Check Sample App](https://github.com/verisoul/react-native-facematch-sample-app) is a demonstration project that showcases a working Verisoul integration in a React Native environment.
## Prerequisites
Before you begin, you'll need:
* Node.js and npm installed on your system
* Git for cloning the repository
* A Verisoul API Key (obtain this by [scheduling a call](https://meetings.hubspot.com/henry-legard))
## Installation Steps
### 1. Clone the Repository
```bash theme={null}
git clone https://github.com/verisoul/react-native-facematch-sample-app.git
cd react-native-facematch-sample-app
```
### 2. Configure API Key
Make sure to use the correct API key for the environment you are using:
```javascript theme={null}
let headers = {
"x-api-key": "YOUR_API_KEY_HERE",
"Content-Type": "application/json"
}
```
### 3. Install Dependencies
Install all required npm packages:
```bash theme={null}
npm install
```
### 4. Run the Application
Start the app via Expo either on a simulator or a physical device:
```bash theme={null}
npx expo start
```
## ID Check Integration
To run the sample app with ID Check functionality, simply add the `?id=true` parameter to the get session URL:
```javascript theme={null}
const faceAndIdSession = await fetch(`https://${API_URL}/liveness/session?id=true`, {
method: 'GET',
headers: headers,
})
```
## What to Expect
Once running, you'll see a demonstration of how Verisoul's Face Match and ID Check work in a React Native environment.
## Next Steps
After exploring the sample app, you can use it as a reference for implementing Verisoul in your own React Native applications.
For more detailed documentation, see the [Verisoul Verification Guide](/verifications/overview).
# React Native Sample App
Source: https://docs.verisoul.ai/examples/react-native-sample-app
Complete reference implementation demonstrating Verisoul integration in a React Native application. Follow the README in the GitHub repository to get started.
# Web Sample App
Source: https://docs.verisoul.ai/examples/web-sample-app
Complete reference implementation demonstrating Verisoul integration in a React application
This guide will help you set up and run the React Sample App, which demonstrates how to integrate Verisoul into a React application.
## About the Sample App
The [Verisoul React Sample App](https://github.com/verisoul/react-sample-app) is a demonstration project built with [create-react-app](https://create-react-app.dev/) that showcases a working Verisoul integration. The app includes:
* A React frontend using Verisoul's script tag
* An Express API server backend
## Prerequisites
Before you begin, you'll need:
* Node.js and npm installed on your system
* Git for cloning the repository
* A Verisoul API Key and Project ID (obtain these by [scheduling a call](https://meetings.hubspot.com/henry-legard))
## Installation Steps
### 1. Clone the Repository
```bash theme={null}
git clone https://github.com/verisoul/react-sample-app.git
cd react-sample-app
```
### 2. Configure Environment Variables
Create a `.env` file from the provided sample and add your Verisoul credentials:
```bash theme={null}
cp .env.sample .env
```
Open the `.env` file in your preferred text editor and fill in the following values:
* `VERISOUL_API_KEY` - Your Verisoul API key
* `VERISOUL_PROJECT_ID` - Your Verisoul project ID
### 3. Install Dependencies
Install all required npm packages:
```bash theme={null}
npm install
```
### 4. Run the Application
Start the development server:
```bash theme={null}
npm start
```
The application will be available at [http://localhost:3000](http://localhost:3000).
## What to Expect
Once running, you'll see a demonstration of how Verisoul's fake user detection works in a React environment. This includes:
* User verification flows
* Integration with Verisoul's JavaScript SDK
* Backend API calls to Verisoul services
## Next Steps
After exploring the sample app, you can use it as a reference for implementing Verisoul in your own React applications.
For more detailed documentation, see the [Verisoul Integration Guide](/integration/overview).
# Multi Accounting Groups
Source: https://docs.verisoul.ai/integration/advanced/multi-accounting-groups
Learn how to segment multi accounting detection within projects using groups
Multi Accounting Groups allow you to segment the multi-accounting graph within a single project. This feature enables you to isolate and manage distinct sets of related accounts, ensuring that multi-accounting detection only occurs within specified groups.
The Verisoul team will tell you if this is the right solution for your use case. Most customers will not need to use this feature.
## How Groups Work
When groups are enabled, accounts are only considered linked if they share the same group identifier. For example:
```text theme={null}
Account 1 (Group A) ← Same device → Account 2 (Group A) // Will be linked
Account 3 (Group B) ← Same device → Account 4 (Group B) // Will be linked
Account 2 (Group A) ← Same device → Account 3 (Group B) // Will NOT be linked
```
Even if accounts from different groups share the same device, network, or other signals, they will not be considered linked accounts.
## Important Restrictions
Before implementing groups, be aware of these critical restrictions:
1. Groups must be enabled for the project, from **Settings → Projects**, then the project, then **Project Settings** in the dashboard. See [Projects and Usage](/dashboard/projects-and-usage).
2. Once groups are enabled for a project, they **cannot** be disabled
3. Every account must have a group assigned
4. Each account can only belong to one group at a time
5. Once an account is added to a group, it cannot be changed to a different group
6. ID Check and Face Match are not supported in the same project with groups enabled
## Implementation
### Client-Side Integration
When initializing the client, include the group in the account object:
```javascript theme={null}
verisoul.account({
id: "user123",
email: "user@example.com",
group: "survey_batch_a" // Group identifier
});
```
### Server-Side Integration
When making authenticate API calls, include the group in the account object:
```javascript theme={null}
{
"account": {
"id": "user123",
"email": "user@example.com",
"group": "survey_batch_a" // Group identifier
}
}
```
## What if I have accounts that are in multiple groups?
A common way to handle this is to create a new account for each account-group pair. This way, you can track the relationship between the account and the group separately.
The easiest way to do this is to concatenate the account id and group together. For example:
```javascript theme={null}
{
"account": {
"id": "user123-survey_batch_a",
"email": "user@example.com",
"group": "survey_batch_a" // Group identifier
}
}
```
You will still want to use the `group` field to segment the multi-accounting graph in the dashboard. Using this method you can still easily search for accounts in the dashboard and segment by group.
## Common Use Cases
### Survey Platforms
The most common use case for groups is survey platforms where:
* Each survey batch needs to be isolated
* Multiple responses from the same user are allowed across different surveys
* Duplicate responses within the same survey need to be prevented
### Alternative Solutions
Before enabling groups, consider these alternatives:
* Using multiple projects instead of groups (recommended for most customers)
* Implementing custom filtering logic in your application
* Using the lists feature for managing allowed duplicates
## Best Practices
1. **Consult First**: Talk to the Verisoul team before enabling groups to ensure it's the right solution for your use case
2. **Plan Ahead**: Since groups cannot be disabled once enabled, carefully plan your implementation
3. **Consistent Naming**: Use a consistent naming convention for your groups
4. **Documentation**: Maintain internal documentation of your group structure and purpose
## Getting Help
If you're considering using groups or have questions about implementation:
* Reach out on Slack or email [support@verisoul.ai](mailto:support@verisoul.ai)
* Schedule a consultation with the Verisoul team
* Review your use case to determine if multiple projects might be a better solution
# Authenticate Endpoint
Source: https://docs.verisoul.ai/integration/backend/authenticated
Get risk decisions for authenticated users with account context
The Authenticate endpoint is the core of Verisoul's fraud prevention system. It allows your backend to verify the risk level of a user session and make informed decisions about allowing or blocking actions when a user is authenticated or associated with an account.
## Overview
The Authenticate endpoint provides comprehensive risk assessment by combining session signals with account-specific intelligence. This makes it the most powerful option for protecting user accounts, sensitive actions, and high-value transactions.
```
POST https://api.prod.verisoul.ai/session/authenticate
```
## When to Use Authenticate
Use the Authenticate endpoint when a session is associated with a user account. This is ideal for:
* **User login flows** - Verify risk before completing authentication
* **Account creation** - Assess risk before finalizing new account registration
* **High-value transactions** - Validate sessions before processing payments, withdrawals, redemptions, etc.
* **Account modifications** - Check risk before allowing password changes or profile updates
* **Sensitive data access** - Verify session legitimacy before granting access to protected information
To avoid false positives, do not authenticate sessions that have been impersonated or are from your internal team members. Authenticating these sessions can result in legitimate accounts being incorrectly flagged for multi-accounting.
## Required Parameters
| Parameter | Type | Description |
| - | - | - |
| `session_id` | string | The Verisoul session ID obtained from the client SDK |
| `account` | object | A valid Verisoul account object |
## Account Object
The account object is a JSON object that contains the following fields:
| Parameter | Required | Type | Description |
| - | - | - | - |
| `id` | Yes | string | Your internal user/account identifier |
| `email` | No | string | User's email address for reputation checks |
| `phone` | No | string | User's phone number in E.164 format (e.g., +12125551234) |
| `metadata` | No | object | Additional context about the user or session |
| `lists` | No | array | Array of list names to add the account to during authentication. Lists will be automatically created if they don't exist. |
### Example Request
```javascript theme={null}
{
"account": {
"id": "niel-test-08-11-2025",
"email": "niel30@beeble.com",
"lists": ["custom-list", "allow"]
},
"session_id": "1de8604b-0c42-41f0-a11c-f26426522d42"
}
```
Lists will be automatically created if they don't exist. Use good naming conventions: lowercase with dashes or underscores, avoiding special characters or whitespace (e.g., `high-risk-users`, `beta_testers`).
## Benefits of Authenticate
* **Comprehensive account assessment** - Get risk scores for both the current session and the account overall
* **Account intelligence** - Leverage email and phone reputation data
* **List integration** - Automatically apply your allow/block lists to decisions
* **Historical context** - Build risk profiles across multiple sessions for the same account
## Next Steps
* [Authenticate Reference](/api-reference/session/authenticate)
* Explore [Lists](/action/lists) for managing allow and block lists
* See the [Example Apps](/examples/web-sample-app) for a complete implementation
# Decision & Scores
Source: https://docs.verisoul.ai/integration/backend/decision
Get decision and scores via API
Once you have a Verisoul session ID from a client SDK, you can pass this session ID to Verisoul's API to get decision. This guide explains how to use Verisoul's decision endpoints effectively.
## When to Use Authenticate
Use the [Authenticate endpoint](/api-reference/session/authenticate) when a session is associated with a user account. This provides the most comprehensive risk assessment by combining session signals with account-specific intelligence.
**Common use cases:**
* **User login flows** - Verify risk before completing authentication
* **Account creation** - Assess risk before finalizing new account registration
* **High-value transactions** - Validate sessions before processing payments
* **Account modifications** - Check risk before allowing password changes or profile updates
* **Sensitive data access** - Verify session legitimacy before granting access to protected information
[Learn more about the Authenticate endpoint →](/api-reference/session/authenticate)
## Next Steps
* Explore [Lists](/action/lists) for managing allow and block lists
* See the [Example Apps](/examples/web-sample-app) for a complete implementation
# Best Practices
Source: https://docs.verisoul.ai/integration/best-practices
Best practices for integrating Verisoul
For optimal fraud detection and user experience, follow these two critical best practices when integrating Verisoul
## 1. Initialize the Client SDK as Early as Possible
**Why this matters:** Early initialization allows Verisoul to collect the maximum amount of behavioral and device signals, resulting in more accurate risk assessments.
### Implementation by Platform
#### Web SDK
Add the Verisoul script to the `
` section of your HTML to ensure it loads before the rest of your page content. For production web integrations, use a [custom hostname](/integration/custom-hostname) so the SDK loads from your own subdomain:
```html theme={null}
```
If your custom hostname is not active yet, you can use `https://js.verisoul.ai/{env}/bundle.js` for sandbox, pilots, or initial testing.
#### Mobile SDK
For our mobile SDKs, initialization happens automatically when the SDK is integrated. Ensure you're following the platform-specific integration guides:
* [iOS](/integration/frontend/ios)
* [Android](/integration/frontend/android)
* [React Native](/integration/frontend/react-native)
* [Flutter](/integration/frontend/flutter)
### Benefits of Early Initialization
* Allows more time for signal collection
* Captures the full user journey
* Improves accuracy of behavioral analysis
* Enables detection of suspicious patterns from the start
## 2. Call the Verisoul API at the Time of Critical Actions
**Why this matters:** While you should initialize the SDK early, you should wait to call the authenticate endpoint until the moment of a critical action.
### Critical Action Examples
* User registration
* User login
* Payment processing
* Rewards redemption
* Account withdrawal
* Profile or settings changes
* High-value transactions
### Implementation Guidance
1. Initialize the SDK early in the user journey
2. Collect the session ID when needed
3. Call the [Authenticate](/integration/backend/authenticated) endpoint at the exact moment of the critical action
4. Use the decision to allow, block, or verify the action
### Why This Approach Works
* **Maximum signal collection**: Client SDKs continuously collect data, so waiting allows more time for signals to be gathered
* **Defeats "human in the loop" fraud**: Fraudsters often create accounts manually but later automate actions
* Example: A fraudster creates an account as a human, verifies their email, and then logs in and starts a bot
* **Captures intent signals**: The most revealing behavioral signals often occur right before a critical action
* **Reduces false positives**: More context leads to more accurate decisions
# Custom Hostname
Source: https://docs.verisoul.ai/integration/custom-hostname
Serve the Verisoul Web SDK from a custom hostname for first-party delivery
Verisoul strongly recommends serving the Web SDK from a custom hostname that you control, such as `js.example.com` or `v.example.com`.
When the SDK is loaded from the default `js.verisoul.ai` hostname, 4-6% of traffic may be blocked by ad blockers or privacy extensions. A custom hostname delivers the SDK from your own domain and helps preserve signal coverage for production integrations.
## Why use a Custom Hostname?
* **First-party context** - Requests originate from a hostname you own, which helps avoid some ad blockers and privacy extensions.
* **Higher signal coverage** - Loading the SDK from your domain helps prevent the 4-6% traffic loss that can happen when extensions block third-party script hostnames.
* **Simpler CSP rules** - All SDK traffic, including runtime API calls, goes through the single hostname you control instead of `*.verisoul.ai`. See the [CSP example](/integration/frontend/browser#content-security-policy-csp) in the browser guide.
* **Brand consistency** - The SDK URL looks like the rest of your application assets.
* **No backend changes** - Your backend still calls Verisoul APIs directly. Only the browser SDK script URL changes.
If your security or infrastructure team has questions about the NS delegation — what is delegated, what records Verisoul publishes, how certificates are issued, and how you stay in control — see [Custom Hostname Security & DNS Delegation](/integration/custom-hostname-security).
## Before you start
You will need:
* Access to your DNS provider.
* A hostname you can delegate to Verisoul, such as `js.example.com` or `v.example.com`.
* Your Verisoul project ID for the environment you are integrating.
Choose a short, ordinary hostname that matches your application domain. Avoid labels that can look suspicious to users or filtering tools, such as `fingerprint`, `telemetry`, or `verisoul`.
## Custom Hostname Setup Guide
Open the Verisoul Dashboard and go to **Settings -> Custom Hostname**.
### 1. Add the hostname
Click **Add Hostname** and enter the hostname you want to use.
For example, if your site is `https://example.com`, common choices are:
* `js.example.com`
* `v.example.com`
The dashboard will create DNS records for the environment you are configuring. In production, the hostname is used directly. In non-production environments, the dashboard may show an environment-specific hostname such as `sandbox.js.example.com`.
In production, Verisoul also prepares the matching sandbox hostname automatically, such as `sandbox.js.example.com`. You only delegate the hostname you entered once.
### 2. Add the nameserver records
The dashboard will show nameserver records. Add each NS record at your DNS provider exactly as shown. The record will follow the following format:
```text theme={null}
Type: NS
Name: js
Value: ns-cloud-a1.googledomains.com
```
You will see four nameserver values. Add all four.
You do not need to add any `_acme-challenge` records. Verisoul manages certificate verification records, A records, and environment-specific hostnames inside the delegated DNS zone.
### 3. Verify and activate
DNS changes can take a few minutes to propagate. Once the records are in place, click **Verify DNS & Activate** in the dashboard.
Verisoul will:
1. Verify that the NS records are visible publicly.
2. Issue the HTTPS certificate.
3. Verify that the hostname routes to Verisoul.
4. Mark the hostname as active.
### 4. Update your integration
Once the hostname is active, update your script tag to load the SDK from your custom hostname:
```html theme={null}
```
Replace `js.example.com` with your custom hostname and `{project_id}` with your Verisoul project ID.
## Troubleshooting
DNS may still be propagating. Confirm that all four NS records were added at the exact hostname shown in the dashboard, then click **Verify DNS & Activate** again.
Confirm that the NS records were added at the hostname shown in the dashboard. Add all four nameserver values.
Recursive DNS resolvers can update at different times. It is normal for one network to see the new route before another network. Use multiple resolvers, such as `1.1.1.1` and `8.8.8.8`, to check propagation.
If there is an old CNAME at the same hostname, it can shadow the NS records. Remove the old CNAME when you are ready to use the NS-delegated custom hostname.
Check whether the hostname resolves publicly and loads over HTTPS:
```bash theme={null}
dig js.example.com A @1.1.1.1
curl -I https://js.example.com/bundle.js
```
If DNS resolves but the request still fails, contact Verisoul support with the hostname and the DNS records shown in the dashboard.
# Android
Source: https://docs.verisoul.ai/integration/frontend/android
Integrating Verisoul in Android Apps
*To run the SDK a Verisoul Project ID is required.* Schedule a call [here](https://meetings.hubspot.com/henry-legard) to get started.
## System Requirements
* Android 7.0 (API level 24) or higher
* Kotlin 1.5 or higher
* Gradle 7.0 or higher
* SDK Size: \~410 KB
## Installation
### Add Maven Repository
Add these lines to your `settings.gradle` file.
```kotlin theme={null}
dependencyResolutionManagement {
repositories {
...
maven { url = uri("https://us-central1-maven.pkg.dev/verisoul/android") }
}
}
```
### Add Dependency
Add these lines to your `build.gradle` file.
#### For Groovy DSL
```kotlin theme={null}
dependencies {
...
implementation "ai.verisoul:android:0.4.74"
}
```
#### For Kotlin DSL
```kotlin theme={null}
dependencies {
...
implementation(libs.verisoul.android)
}
```
Add these lines to your `libs.versions.toml` file.
Under the `[versions]` add:
```kotlin theme={null}
verisoul = "0.4.74"
```
Under the `[libraries]` add:
```kotlin theme={null}
verisoul-android = { group = "ai.verisoul", name = "android", version.ref = "verisoul" }
```
## Usage
### Initialize the SDK
Call `init()` before you intend to retrieve a Verisoul session\_id. The example below initializes in your `Application` class's `onCreate()` method; if you do this, make sure to register the Application class in your `AndroidManifest.xml`.
**Application class:**
```kotlin theme={null}
import ai.verisoul.sdk.Verisoul
import ai.verisoul.sdk.VerisoulEnvironment
class SampleApplication : Application() {
override fun onCreate() {
super.onCreate()
Verisoul.init(
this,
VerisoulEnvironment.Prod, // or VerisoulEnvironment.Sandbox
"your-project-id"
)
}
}
```
**AndroidManifest.xml:**
```xml theme={null}
...
```
The `init()` method initializes the Verisoul SDK with your project credentials. Only initialize the SDK if you plan to retrieve a session\_id and call the Verisoul API for that session.
**Parameters:**
* `context`: Your application context
* `environment`: The environment to use `VerisoulEnvironment.Prod` for production or `VerisoulEnvironment.Sandbox` for testing
* `projectId`: Your unique Verisoul project identifier
### Get Session ID
The `getSessionId()` method returns the current session identifier after the SDK has collected sufficient device data. This session ID is required to request a risk assessment from Verisoul's API.
**Important Notes:**
* Session IDs are short-lived and expire after 24 hours
* The session ID becomes available once minimum data collection is complete (typically within seconds)
* You should send this session ID to your backend, which can then call Verisoul's API to get a risk assessment
**Example:**
```kotlin theme={null}
Verisoul.getSessionId(
callback = object : VerisoulSessionCallback {
override fun onSuccess(sessionId: String) {
// Send sessionId to your backend for risk assessment
}
override fun onFailure(exception: Exception) {
// Handle exception
}
}
)
```
### Provide Touch Events
In order to gather touch events and compare them to device accelerometer sensor data, the app will need to provide touch events to Verisoul. The way to achieve this is to create `BaseActivity`, to override `dispatchTouchEvent` function and pass the data to Verisoul like shown below.
```kotlin theme={null}
open class BaseActivity : Activity() {
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
Verisoul.onTouchEvent(event)
return super.dispatchTouchEvent(event)
}
// Other common BaseActivity code...
}
```
In the application, just use BaseActivity as an Activity base class.
```kotlin theme={null}
class MainActivity : BaseActivity() {
// Other Activity code...
}
```
### Error Codes
The SDK throws `VerisoulException` with the following error codes:
| Error Code | Description | Recommended Action |
| - | - | - |
| INVALID\_ENVIRONMENT | The environment parameter passed to `Verisoul.init()` is invalid. Valid values are "dev", "sandbox", or "prod". | Ensure the environment parameter is exactly "dev", "sandbox", or "prod" (case-sensitive, no whitespace). |
| SESSION\_UNAVAILABLE | A valid session ID could not be obtained. This typically occurs when Verisoul's servers are unreachable due to network blocking or a very slow connection. | Implement exponential backoff. Prompt user to check network or disable network blocker. |
| WEBVIEW\_UNAVAILABLE | WebView is not available on the device. This can occur when WebView is disabled, missing, uninstalled, or corrupted on the device. | Prompt user to enable WebView in settings, update Android System WebView, or switch devices. |
| WEBVIEW\_RENDERER\_CRASHED | The WebView renderer process crashed, typically due to low memory or resource pressure. | Retry the request (SDK will attempt recovery), encourage users to update Android System WebView, or switch devices. |
#### Exception Structure
All errors are thrown as `VerisoulException` with the following properties:
| Property | Type | Description |
| - | - | - |
| code | String | One of the error codes above |
| message | String | Human-readable error description |
| cause | Throwable? | The underlying exception that caused the error (if any) |
## Google Play Data Safety
For information on how to complete the Google Play Data Safety section when using the Verisoul SDK, please refer to our [Data Safety Instructions](https://support.verisoul.ai/articles/2377394661-verisoul-android-sdk-play-store-data-safety-instructions).
## Example
For a complete working example, see the [Android Sample App](/examples/android-sample-app).
# Browser
Source: https://docs.verisoul.ai/integration/frontend/browser
Integrating Verisoul in Web Applications
Integrating Verisoul in web browsers is straightforward and requires minimal code changes. This guide covers everything you need to implement Verisoul's fraud prevention in your web application.
## Installation
For production web integrations, we strongly recommend setting up a [custom hostname](/integration/custom-hostname) and loading the SDK from your own subdomain. Without a custom hostname, 4-6% of traffic may be blocked by ad blockers or privacy extensions.
### Script Tag Installation
There are two options for installing the Verisoul script in your HTML. You should place the script tag in the `` as early as possible to ensure the Verisoul object is available on the window before calling Verisoul functions.
Use your custom hostname for production traffic:
```html theme={null}
```
If you have not set up a custom hostname yet, you can use the Verisoul-hosted SDK URL for sandbox, pilots, or initial testing:
```html theme={null}
```
#### Async Installation (Recommended)
The async installation provides better page load performance by loading the script asynchronously, preventing it from blocking the page render.
This option includes a helper script that ensures the Verisoul object is always available on the window, eliminating the need to await the script load before calling Verisoul functions. The helper script is not required but will generally make the integration easier.
```html theme={null}
```
#### Synchronous Installation
If you prefer a slightly simpler integration, you can use the basic script tag. Note that this will block page rendering until the script is loaded. The Verisoul object will be available on the window immediately after the script is loaded.
```html theme={null}
```
#### Script Tag Parameters
Replace the following parameters in either installation method:
* `{custom_hostname}`: Your configured custom hostname, such as `js.example.com`. Follow the [Custom Hostname](/integration/custom-hostname) guide to set this up.
* `{env}`: Use either `prod` or `sandbox` when loading from the Verisoul-hosted URL, `https://js.verisoul.ai/{env}/bundle.js`
* `{project_id}`: Your project ID, which must match the environment
### Content Security Policy (CSP)
If your application has a Content Security Policy, update it to allow the SDK.
With a custom hostname, all SDK traffic — the script itself and its runtime API calls — goes through your custom hostname, so a single hostname covers everything:
```html theme={null}
```
If you are using the Verisoul-hosted SDK URL before your custom hostname is active, allow the Verisoul domains instead:
```html theme={null}
```
## Usage
Once installed, you can use the SDK to get the current `session_id` when you need to make a prediction.
### session()
If you are looking to get started quickly or to Pilot Verisoul, see [account()](#account) for a simplified integration.
Verisoul generates a `session_id` to uniquely identify each user session. The `session_id` is required when you need to get a risk prediction from the backend API and must be passed from your client to the server.
You can get the current `session_id` at any time by calling `Verisoul.session()`. The function returns a promise that resolves once Verisoul collects a minimum amount of session information to make a prediction.
**Important**: Each `session_id` expires after 24 hours and cannot be used to call the backend API after expiration. It is recommended to only get a `session_id` right before you need to make a server-side request.
**Example usage:**
```javascript theme={null}
const login = async () => {
try {
const {session_id} = await window.Verisoul.session();
// pass session_id to backend
} catch (e) {
console.log("Verisoul failed get session_id", e);
}
}
```
### account()
This function is intended **only** for customers piloting Verisoul. Do not use for long term production use or real-time fraud prevention.
The `account()` function provides a simplified way to send user account information to Verisoul directly from the client side. While easy to integrate, this method has important limitations:
* **Offline analysis only**: Data sent via `account()` is only visible in the Verisoul dashboard
* **No real-time decisions**: Unlike the server-side API, this method doesn't allow your application to receive and act on Verisoul's risk scores in real-time
* **Limited use case**: Designed specifically for initial pilots and evaluation purposes
The function accepts the [Account Object](/api-reference/types/account) which is the same object used in the [authenticate API](/api-reference/session/authenticate), making it easy to transition to the server-side integration later.
Only the `id` field is required.
**Example usage:**
```javascript theme={null}
const login = async () => {
// Send account information to Verisoul for dashboard analysis
await window.Verisoul.account({
id: "example-account-id",
email: "example@example.com",
lists: ["beta-users", "allow"],
metadata: {
"paid": true,
}
});
}
```
For production environments, we strongly recommend implementing the full server-side integration to leverage Verisoul's real-time fraud prevention capabilities.
## Next Steps
* Learn about [Backend Integration](/integration/backend/authenticated) for API implementation
* See Integration [Best Practices](/integration/best-practices)
* Explore [Mobile Integration](/integration/frontend/android) for native apps
* See the [Web Sample App](/examples/web-sample-app) for a complete implementation
# Flutter
Source: https://docs.verisoul.ai/integration/frontend/flutter
Integrating Verisoul in Flutter Apps
*To run the SDK a Verisoul Project ID is required.* Schedule a call [here](https://meetings.hubspot.com/henry-legard) to get started.
## System Requirements
* Flutter SDK 3.0 or higher
* Dart 2.17 or higher
* iOS 14.0 or higher
* Android API level 24 (Android 7.0) or higher
* For Web: Modern browsers with JavaScript enabled
* SDK Size: \~120 KB (plus native iOS and Android SDK dependencies)
## Installation
### Add Dependency
```yaml theme={null}
dependencies:
flutter:
sdk: flutter
# ...
verisoul_sdk: 0.4.72
```
### Android Configuration
#### 1. Update Minimum SDK Version
Update the Android minimum `minSdk` to **24** in `android/app/build.gradle`:
```groovy theme={null}
defaultConfig {
minSdk = 24
//...
}
```
#### 2. Add Maven Repository
If an exception occurs during the build stating that the `ai.verisoul:android` package cannot be downloaded, add the following Maven repository inside your `android/build.gradle` file:
```groovy theme={null}
allprojects {
repositories {
// ...
maven { url = uri("https://us-central1-maven.pkg.dev/verisoul/android") }
}
}
```
### iOS Configuration
For iOS-specific configuration including Device Check and App Attest setup, please refer to the [iOS SDK Documentation](/integration/frontend/ios#ios-device-check).
## Usage
### Initialize the SDK
Call `configure()` before you intend to retrieve a Verisoul session\_id:
```dart theme={null}
import 'package:verisoul_sdk/verisoul.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
VerisoulSdk.configure(projectId: "Project ID ", environment: VerisoulEnvironment.prod);
runApp(const MyApp());
}
```
The `configure()` method initializes the Verisoul SDK with your project credentials. Only initialize the SDK if you plan to retrieve a session\_id and call the Verisoul API for that session.
**Parameters:**
* `projectId`: Your unique Verisoul project identifier
* `environment`: The environment to use `VerisoulEnvironment.prod` for production or `VerisoulEnvironment.sandbox` for testing
### Get Session ID
The `getSessionApi()` method returns the current session identifier after the SDK has collected sufficient device data. This session ID is required to request a risk assessment from Verisoul's API.
**Important Notes:**
* Session IDs are short-lived and expire after 24 hours
* The session ID becomes available once minimum data collection is complete (typically within seconds)
* You should send this session ID to your backend, which can then call Verisoul's API to get a risk assessment
**Example:**
```dart theme={null}
final session = await VerisoulSdk.getSessionApi();
```
### Provide Touch Events
Wrap your App with `VerisoulWrapper` to automatically capture touch events:
```dart theme={null}
runApp(VerisoulWrapper(child: const MyApp()));
```
## Web Support
### Add Verisoul Script
Add the Verisoul script to your `web/index.html`:
```html theme={null}
```
**Replace the following parameters:**
* **`{env}`**: Use either `prod` or `sandbox`
* **`{project_id}`**: Your project ID, which must match the environment
### Content Security Policy (CSP)
If your application has a Content Security Policy, update it to include the following Verisoul domains:
```html theme={null}
```
### Set Account Data (Web Only)
The `setAccountData()` function provides a simplified way to send user account information to Verisoul directly from the client side. While easy to integrate, this method has important limitations:
* **Offline analysis only**: Data sent via account() is only visible in the Verisoul dashboard
* **No real-time decisions**: Unlike the server-side API, this method doesn't allow your application to receive and act on Verisoul's risk scores in real-time
* **Limited use case**: Designed specifically for initial pilots and evaluation purposes
```dart theme={null}
await VerisoulSdk.setAccountData(
id: "example-id",
email: "example@example.com",
metadata: {"paid": true}
);
```
### Error Codes
The SDK throws `VerisoulException` with the following error codes:
| Error Code | Description | Recommended Action |
| - | - | - |
| INVALID\_ENVIRONMENT | The environment parameter passed to `Verisoul.configure()` is invalid. Valid values are "dev", "sandbox", or "prod". | Ensure the environment parameter is exactly "dev", "sandbox", or "prod" (case-sensitive, no whitespace). |
| SESSION\_UNAVAILABLE | A valid session ID could not be obtained. This typically occurs when Verisoul's servers are unreachable due to network blocking or a very slow connection. | Implement exponential backoff. Prompt user to check network or disable network blocker. |
| WEBVIEW\_UNAVAILABLE | WebView is not available on the device. This can occur when WebView is disabled, missing, uninstalled, or corrupted on the device. | Prompt user to enable WebView in settings, update Android System WebView, or switch devices. |
| WEBVIEW\_RENDERER\_CRASHED | The WebView renderer process crashed, typically due to low memory or resource pressure. | Retry the request (SDK will attempt recovery), encourage users to update Android System WebView, or switch devices. |
#### Exception Structure
All errors are thrown as `VerisoulException` with the following properties:
| Property | Type | Description |
| - | - | - |
| code | String | One of the error codes above |
| message | String | Human-readable error description |
| cause | Throwable? | The underlying exception that caused the error (if any) |
## Example
For a complete working example, see the [Flutter Sample App](/examples/flutter-sample-app).
## Additional Resources
* [Verisoul Flutter SDK on Pub.dev](https://pub.dev/packages/verisoul_sdk)
# iOS
Source: https://docs.verisoul.ai/integration/frontend/ios
Integrating Verisoul in iOS Apps
*To run the SDK a Verisoul Project ID is required.* Schedule a call [here](https://meetings.hubspot.com/henry-legard) to get started.
## System Requirements
* iOS 14.0 or higher
* Xcode 15.0 or higher
* Swift 5.9 or higher
* CocoaPods 1.10+ (if using CocoaPods)
* SDK Size: \~480 KB
## Installation
You can install VerisoulSDK in your iOS project using either CocoaPods or Swift Package Manager.
### CocoaPods
To integrate VerisoulSDK with CocoaPods:
1. Ensure CocoaPods is installed on your machine. If not, run:
```sh theme={null}
sudo gem install cocoapods
```
2. Add VerisoulSDK to your Podfile:
```ruby theme={null}
pod 'VerisoulSDK', '~> 0.4.71'
```
3. Run the following command to install the SDK:
```sh theme={null}
pod install
```
4. Open the `.xcworkspace` file in Xcode and start using the SDK.
### Swift Package Manager (SPM)
To integrate VerisoulSDK using Swift Package Manager:
1. Open your project in Xcode.
2. Go to `File > Add Packages`.
3. Enter the repository URL for VerisoulSDK:
```url theme={null}
https://github.com/verisoul/ios-sdk.git
```
4. Choose the version you wish to use and add the package.
The SDK will automatically integrate into your project.
### iOS Device Check
To fully utilize the Verisoul SDK, you must add the `App Attest` capability to your project. This capability allows the SDK to perform necessary checks and validations to ensure the integrity and security of your application.
Update your app's entitlements file:
```xml theme={null}
com.apple.developer.devicecheck.appattest-environment
production
```
## Usage
### Initialize the SDK
Call `configure()` before you intend to retrieve a Verisoul session\_id.
```swift theme={null}
import VerisoulSDK
Verisoul.shared.configure(env: .prod, projectId: "your-project-id")
```
The `configure()` method initializes the Verisoul SDK with your project credentials. Only initialize the SDK if you plan to retrieve a session\_id and call the Verisoul API for that session.
**Parameters:**
* `env`: The environment to use `.prod` for production or `.sandbox` for testing
* `projectId`: Your unique Verisoul project identifier
* `reinitialize`: Whether to create a new session during configuration. Defaults to `false`
* `collectTouchData`: Whether to begin touch and motion capture. Defaults to `true`
### Get Session ID
The `session()` method returns the current session identifier after the SDK has collected sufficient device data. This session ID is required to request a risk assessment from Verisoul's API.
**Important Notes:**
* Session IDs are short-lived and expire after 24 hours
* The session ID becomes available once minimum data collection is complete (typically within seconds)
* You should send this session ID to your backend, which can then call Verisoul's API to get a risk assessment
**Example:**
```swift theme={null}
do {
let sessionId = try await Verisoul.shared.session()
// Send sessionId to your backend for risk assessment
print("Session ID: \(sessionId)")
} catch {
print("Failed to retrieve session ID: \(error)")
}
```
### Provide Touch Events
Starting in version 0.4.71, the Verisoul SDK captures a small number of touch
events together with accelerometer samples without adding any views to your
app's hierarchy. It attaches a `UIGestureRecognizer` to the key window with
`cancelsTouchesInView`, `delaysTouchesBegan`, and `delaysTouchesEnded` all set
to `false`, so your app continues to receive touches without cancellation or
delay.
Touch and motion capture is enabled by default: collection begins
automatically as soon as `configure()` is called.
To configure the SDK without starting touch and motion capture:
```swift theme={null}
Verisoul.shared.configure(
env: .prod,
projectId: "your-project-id",
collectTouchData: false
)
```
Use `setTouchCaptureEnabled(_:)` to limit capture to a specific flow such as
signup:
```swift theme={null}
Verisoul.shared.configure(
env: .prod,
projectId: "your-project-id",
collectTouchData: false
)
// When signup begins:
Verisoul.shared.setTouchCaptureEnabled(true)
// When signup ends:
Verisoul.shared.setTouchCaptureEnabled(false)
```
### Error Codes
The SDK throws `VerisoulException` with the following error codes:
| Error Code | Description | Recommended Action |
| - | - | - |
| INVALID\_ENVIRONMENT | The environment parameter passed to `Verisoul.init()` is invalid. Valid values are "dev", "sandbox", or "prod". | Ensure the environment parameter is exactly "dev", "sandbox", or "prod" (case-sensitive, no whitespace). |
| SESSION\_UNAVAILABLE | A valid session ID could not be obtained. This typically occurs when Verisoul's servers are unreachable due to network blocking or a very slow connection. | Implement exponential backoff. Prompt user to check network or disable network blocker. |
| WEBVIEW\_UNAVAILABLE | WebView is not available on the device. This can occur when WebView is disabled, missing, uninstalled, or corrupted on the device. | Prompt user to enable WebView in settings, update Android System WebView, or switch devices. |
#### Exception Structure
All errors are thrown as `VerisoulException` with the following properties:
| Property | Type | Description |
| - | - | - |
| code | String | One of the error codes above |
| message | String | Human-readable error description |
| cause | Throwable? | The underlying exception that caused the error (if any) |
## Example
For a complete working example, see the [iOS Sample App](/examples/ios-sample-app).
## Additional Resource
* [Verisoul CocoaPods](https://cocoapods.org/pods/VerisoulSDK)
# React Native
Source: https://docs.verisoul.ai/integration/frontend/react-native
Integrating Verisoul in React Native Apps
*To run the SDK a Verisoul Project ID is required.* Schedule a call [here](https://meetings.hubspot.com/henry-legard) to get started.
## System Requirements
* React Native 0.60 or higher
* iOS 14.0 or higher
* Android API level 21 (Android 5.0) or higher
* For Expo projects: Expo SDK 45 or higher with custom development client
* SDK Size: \~90 KB (plus native iOS and Android SDK dependencies)
## Installation
#### Using NPM
```sh theme={null}
npm install @verisoul_ai/react-native-verisoul
```
#### Using Yarn
```sh theme={null}
yarn add @verisoul_ai/react-native-verisoul
```
#### Android Configuration
If an exception occurs during the build stating that the `ai.verisoul:android` package cannot be downloaded, add the following Maven repository inside your `android/build.gradle` file:
```groovy theme={null}
allprojects {
repositories {
// ...
maven { url = uri("https://us-central1-maven.pkg.dev/verisoul/android") }
}
}
```
### Expo Projects
> **Note:** Requires custom development client (not supported in Expo Go)
**1. Install expo-dev-client:**
```sh theme={null}
npx expo install expo-dev-client
```
**2. Configure `app.json`: add the plugin and set `newArchEnabled` to `true`**
```json theme={null}
{
"expo": {
"newArchEnabled": true,
"plugins": ["@verisoul_ai/react-native-verisoul"]
}
}
```
**3. Install Verisoul SDK:**
```sh theme={null}
npm install @verisoul_ai/react-native-verisoul
```
**4. Prebuild and run:**
```sh theme={null}
npx expo prebuild --clean
npx expo run:android # or npx expo run:ios
```
## Usage
### Initialize the SDK
The `configure()` method initializes the Verisoul SDK with your project credentials. Call `configure()` before you intend to retrieve a Verisoul session\_id — only initialize the SDK if you plan to call the Verisoul API for that session.
**Parameters:**
* `environment`: The environment to use `VerisoulEnvironment.prod` for production or `VerisoulEnvironment.sandbox` for testing
* `projectId`: Your unique Verisoul project identifier
**Example:**
```js theme={null}
import Verisoul, {
VerisoulEnvironment,
} from "@verisoul_ai/react-native-verisoul";
useEffect(() => {
Verisoul.configure({
environment: VerisoulEnvironment.prod, // or VerisoulEnvironment.sandbox
projectId: "YOUR_PROJECT_ID",
});
}, []);
```
When called, the Verisoul SDK will initialize its components, begin collecting device telemetry data, and prepare a session ID for fraud assessment.
### getSessionId()
The `getSessionID()` method returns the current session identifier after the SDK has collected sufficient device data. This session ID is required to request a risk assessment from Verisoul's API.
**Important Notes:**
* Session IDs are short-lived and expire after 24 hours
* The session ID becomes available once minimum data collection is complete (typically within seconds)
* You should send this session ID to your backend, which can then call Verisoul's API to get a risk assessment
* A new session ID is generated each time the SDK is initialized
**Example:**
```js theme={null}
const sessionId = await Verisoul.getSessionID();
```
### Provide Touch Events
Touch event data is collected and analyzed to detect automated/bot behavior by comparing touch patterns with device sensor data. This helps identify anomalies that may indicate fraud.
**React Native Setup:**
Wrap your root component with `VerisoulTouchRootView` to automatically capture touch events across both iOS and Android:
```js theme={null}
import { VerisoulTouchRootView } from "@verisoul_ai/react-native-verisoul";
function App() {
return (
{/* Your app components */}
);
}
```
#### iOS Configuration
For iOS-specific configuration including Device Check and App Attest setup, please refer to the [iOS SDK Documentation](/integration/frontend/ios#ios-device-check).
### Error Codes
The SDK throws `VerisoulException` with the following error codes:
| Error Code | Description | Recommended Action |
| - | - | - |
| INVALID\_ENVIRONMENT | The environment parameter passed to `Verisoul.configure()` is invalid. Valid values are "dev", "sandbox", or "prod". | Ensure the environment parameter is exactly "dev", "sandbox", or "prod" (case-sensitive, no whitespace). |
| SESSION\_UNAVAILABLE | A valid session ID could not be obtained. This typically occurs when Verisoul's servers are unreachable due to network blocking or a very slow connection. | Implement exponential backoff. Prompt user to check network or disable network blocker. |
| WEBVIEW\_UNAVAILABLE | WebView is not available on the device. This can occur when WebView is disabled, missing, uninstalled, or corrupted on the device. | Prompt user to enable WebView in settings, update Android System WebView, or switch devices. |
| WEBVIEW\_RENDERER\_CRASHED | The WebView renderer process crashed, typically due to low memory or resource pressure. | Retry the request (SDK will attempt recovery), encourage users to update Android System WebView, or switch devices. |
#### Exception Structure
All errors are thrown as `VerisoulException` with the following properties:
| Property | Type | Description |
| - | - | - |
| code | String | One of the error codes above |
| message | String | Human-readable error description |
| cause | Throwable? | The underlying exception that caused the error (if any) |
## Example
For a complete working example, see the [React Native Sample App](/examples/react-native-sample-app).
## Additional Resources
* [Verisoul NPM](https://www.npmjs.com/package/@verisoul_ai/react-native-verisoul)
# Integration Overview
Source: https://docs.verisoul.ai/integration/overview
How to integrate Verisoul into your application
This guide provides a comprehensive overview of Verisoul's fraud prevention system and how to integrate it into your application.
## Integration Flow
Add the Verisoul SDK to your application and initialize it early in your app's lifecycle.
Supported platforms: Web, iOS, Android, React Native, Flutter, WebView
After initialization, the Verisoul SDK automatically begins collecting signals in the background with minimal impact on performance:
Fingerprint network interfaces, IP addresses, and geolocation
Fingerprint device properties, hardware, and software
Continuously monitor mouse moves, touches, and keystrokes
Get a unique and short-lived session token for the current user session
- Required for all API calls to our backend
- Resistant to replay attacks
Retrieve the session ID at critical points in your user journey such as sign up, login, withdrawal requests, or high-value transactions.
Make a secure call to our API from your backend server with the session\_id and optionally user identifiers
-
Final decision categorized as:
- Fake: High probability of fraud
- Suspicious: Elevated risk indicators
- Real: Legitimate user
- Proxies, VPNs, Tor, Datacenters
- Impossible Travel
- Emulators and Fake Devices
- Automated Bots
- Linked Accounts
Based on the Verisoul response, segment users based on your use case:
Block access or implement step-up verification for users with high probability of fraud
Monitor activity closely and consider additional verification for high-value actions
Allow seamless access without unnecessary friction
## Ready to Begin?
Detailed guide for implementing the client-side SDK in web and mobile applications
Step-by-step instructions for server-side authentication and risk assessment
Complete reference implementation demonstrating best practices
# Troubleshooting
Source: https://docs.verisoul.ai/integration/troubleshooting
Common issues and how to troubleshoot them
This guide covers common issues you might encounter when using Verisoul and provides solutions to resolve them. If your issue isn't listed here, please reach out in your customer Slack channel for assistance.
## Browser SDK Issues
### Verisoul is undefined
**Problem:** The Verisoul script failed to load properly.
**Possible causes:**
* User is actively blocking Verisoul
* Slow network connection
* User is located in an OFAC or sanctioned country (Cuba, North Korea, etc.) that may be blocked by our CDN provider
**Solution:**
1. Retry loading the script tag
2. If the issue persists, prevent the user from proceeding with the critical action
### Unable to get session ID when calling Verisoul.session()
**Problem:** Cannot retrieve a Verisoul session ID.
**Context:**
Verisoul only returns a session ID after collecting the minimum information required to make a decision. This takes less than 500ms on most devices. The minimum payload only collects certain device/network attributes and sends that data to a globally deployed edge endpoint.
**Solution:**
1. Retry the request
2. If the session ID is still not returned after retrying, this may indicate that Verisoul is being blocked by a malicious user
3. In this case, prevent the user from proceeding with the critical action
We are not aware of any ad blockers that would block Verisoul. If you are experiencing this issue, please reach out to us in the customer Slack channel.
## Verisoul API Issues
### Session ID Not Found
**Error message:**
```json theme={null}
{
"statusCode": 400,
"message": "Session ID not found"
}
```
**Problem:** The system cannot locate the provided session ID.
**Solution:**
This typically happens when the project, environment, or API key do not match. Verify that you're using the correct project, environment, and API key.
### Session ID Expired
**Error message:**
```json theme={null}
{
"statusCode": 400,
"message": "Session ID has expired"
}
```
**Problem:** The Verisoul session has expired.
**Solution:**
Session IDs expire 24 hours after creation. You should only retrieve a session ID when you're about to call the Verisoul API to get a decision.
# How Verisoul Works
Source: https://docs.verisoul.ai/overview/how-verisoul-works
The main components of Verisoul's solution
## Comprehensive Fraud Prevention
Verisoul provides a seamless way to understand an application's fake user prevalence and take the first steps toward preventing fake user harm. The client SDK and backend API enable continuous monitoring and prevention of multi-accounting, bots and high risk accounts.
It's critical for applications to prevent fake accounts both at the onset and at critical moments in an account's lifespan.
The Verisoul platform provides an all-in-one fake user prevention solution that can adapt to a range of needs all with a single quick integration.
Identify and prevent bots from accessing your platform
Keep fraudsters out with advanced risk detection
Prevent duplicate accounts and multi-accounting
## How It Works
### Client-Side SDK
Web and mobile frontend SDKs integrate with just a few lines of code:
* No impact on user experience and low performance overhead
* Automatically collects device and network data
* Continuously monitors user behavior patterns
### Identity Data Collection
Verisoul can optionally ingest account ID, email address, phone number and name:
* Risk scoring (VoIP, Disposable, Reputation)
* Identity matching to detect multiple account creation
* Identity verification workflows
### ML Decisions & Scores
Backend API returns risk scores and decisions in real time:
* Rich set of signals for all vectors of fraud
* ML models are tuned specifically for your application
* Benefit from our consortium of customers
### Lists & Rules
Customize your fraud prevention strategy with:
* Custom blocklists and allowlists
* Configurable rule sets
* Real-time rule enforcement
### Dashboard
Real-time dashboard for data analysis and customer support investigations:
* Action accounts in real time through lists and workflows
* Detailed analytics and reporting
* No-code rule creation, with backtesting on your own data
* An AI agent that investigates, reports and acts on lists
### Verifications
Automate fraud workflows with fully managed verifications:
* Phone number VoIP detection and risk scoring
* Facial matching and liveness detection
* Identity document verification
## Integration Journey
Get started with our simple integration process in minutes
Tailor the solution to your specific business needs
Full API access for advanced integrations
Monitor and analyze fraud patterns in real-time
Ready to get started? Check out our [integration guide](/integration/overview) or [schedule a demo](https://verisoul.com/book-a-demo) with our team.
# Where to Integrate Verisoul
Source: https://docs.verisoul.ai/overview/where-to-integrate
Learn about the key integration points for Verisoul in your platform
Verisoul can be integrated at various critical points in your user journey to provide comprehensive protection against fraud and account abuse.
Signups & Logins
Protect your platform at the entry points where users first interact with your service.
Verify new users during registration to prevent fake accounts
Authenticate returning users for continuous protection
Secure password recovery flows to prevent unauthorized access
Validate third-party authentication to prevent identity spoofing
Payments & Payouts
Secure financial transactions to prevent fraud and protect your revenue streams.
Verify user identity before processing payments
Validate withdrawal requests to prevent unauthorized fund transfers
Protect recurring payment setups from fraudulent accounts
Verify legitimacy of refund requests to prevent fraud
User Monitoring
Continuous protection throughout the user lifecycle to detect and prevent account abuse.
Track user sessions to detect suspicious activities
Monitor user behavior patterns to identify anomalies
Analyze user interactions to detect fraudulent behavior
Continuously evaluate account risk levels
## Implementation Recommendations
Integrate the Verisoul SDK at the earliest point in your user journey to establish a baseline for user identity and behavior.
This allows maximum protection from the moment a user interacts with your platform.
Call Verisoul's API to check the user integrity before allowing high-value or sensitive actions like payments, logins, or data access.
This adds an extra layer of security at critical touchpoints in the user journey.
Set up automated workflows to respond to high-risk behavior, such as step-up authentication, account restrictions, or manual review.
Automated responses ensure immediate action when suspicious activity is detected.
Ready to integrate Verisoul into your platform? Check out our [integration guide](/integration/overview) or [contact our team](https://verisoul.com/contact) for personalized assistance.
# Account vs Session
Source: https://docs.verisoul.ai/signals-scores/account-vs-session
Understanding the difference between account and session signals in Verisoul
Accounts and Sessions are the fundamental data entities in Verisoul. Understanding this distinction is crucial for implementing effective fraud prevention strategies.
## How Accounts and Sessions Work Together
Every webpage or app interaction gets a `session_id` on the client side. This session is then later tied to an account (`account_id`).
* **Sessions** represent a point in time interaction with your application
* **Accounts** represent persistent user identities that span multiple sessions
Sessions are typically only tied to a single account but this is not enforced via the API.
## Key Differences
| Account | Session |
| - | - |
| Persistent identity | Temporary interaction |
| Aggregate of all sessions | Data from a single client-side interaction |
| Spans multiple sessions | Limited time window (24 hours) |
| Can be deleted and updated | Cannot be modified once created |
| Can be added to lists | Cannot be added to lists |
## Session Signals
Session signals are derived purely from data collected from the webpage or application without requiring information about the account\_id or other sessions.
* **Device Information**: Hardware, software, and browser characteristics
* **Network information**: IP address, connection type, service provider
* **Location information**: Geolocation, country, state, city, zip code
* **Behavioral data**: Mouse movements, typing patterns, touch interactions
### Types of Session Signals
Session signals fall into two main categories:
1. **Session Data**: Raw information collected during the session
2. **Session Scores**: Risk evaluations derived from the session data
#### Session Data
Session data includes detailed information about the user's environment and behavior:
```json theme={null}
"session": {
"start_time": "2025-03-12T16:56:21.853Z",
"true_country_code": "US",
"network": {
"ip_address": "2600:1700:261:b810:4d06:c3c4:86e:ffc",
"service_provider": "AT&T Internet",
"connection_type": "isp"
},
"location": {
"continent": "NA",
"country_code": "US",
"state": "Texas",
"city": "Austin",
"zip_code": "78703",
"timezone": "America/Chicago",
"latitude": 30.2881,
"longitude": -97.764
},
"browser": {
"type": "Chrome",
"version": "132.0.0.0",
"language": "en",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
"timezone": "America/Chicago"
},
"device": {
"category": "desktop",
"type": "Mac",
"os": "macOS 10.15.7",
"cpu_cores": 16,
"memory": 8,
"gpu": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4 Max, Unspecified Version)"
},
"bot": {
"mouse_num_events": 536,
"click_num_events": 8,
"keyboard_num_events": 2,
"touch_num_events": 0,
"clipboard_num_events": 2
}
}
```
#### Session Scores
Session scores are risk evaluations derived from analyzing the session data:
```json theme={null}
"risk_signals": {
"device_risk": false,
"proxy": false,
"vpn": false,
"tor": false,
"spoofed_ip": false,
"datacenter": false,
"recent_fraud_ip": false,
"impossible_travel": false,
"device_network_mismatch": false
},
"risk_signal_scores": {
"device_risk": 0.0295,
"proxy": 0,
"vpn": 0,
"tor": 0,
"datacenter": 0,
"recent_fraud_ip": 0,
"impossible_travel": 0,
"device_network_mismatch": 0.0001,
"ip_reputation": 0.0113
}
```
Projects with risk signal details enabled also receive a `risk_signal_details` object — named reason codes that explain *why* the network and device scores are elevated:
```json theme={null}
"risk_signal_details": {
"network": ["true_country_mismatch", "ip_on_vpn_list"],
"device": []
}
```
See [Risk Signal Details](/signals-scores/risk-signals) for the full catalog of signals and what each represents.
## Account Signals
Account signals are an aggregate of all the sessions associated with an account\_id. They also include information that can only be tied to the account level.
### What Are Account Signals?
Account signals include:
* **Identity information**: Email domains, usernames, account creation patterns
* **Cross-session patterns**: How the user behaves across multiple sessions
* **Linked accounts**: Connections to other accounts in your system
* **Impossible travel**: Login attempts from geographically impossible locations
* **Email risk**: Risk associated with the email address
* **List membership**: Inclusion in allow/block lists
* **Device and network uniqueness**: Number of unique devices and networks used over time periods
### Example of Account Signals
Account signals provide a comprehensive view of user activity across multiple sessions:
```json theme={null}
{
"project_id": "00000000-0000-0000-0000-000000000001",
"request_id": "e87bcdbd-e6f8-4ac1-aa26-ebe6126162d7",
"account": {
"id": "test-account",
"email": "",
"metadata": {}
},
"num_sessions": 4,
"first_seen": "2025-02-20T03:06:59.679Z",
"last_seen": "2025-03-12T16:56:45.094Z",
"last_session": "8d89023f-959a-4cc5-bf3a-e20cfe26b006",
"country": "US",
"countries": [
"US"
],
"decision": "Fake",
"account_score": 0.9409,
"bot": 0,
"multiple_accounts": 1,
"risk_signals": 0.2235,
"accounts_linked": 35,
"lists": [
"custom_list"
],
"unique_devices": {
"1_day": 1,
"7_day": 2
},
"unique_networks": {
"1_day": 1,
"7_day": 2
},
"email": {
"email": "",
"disposable": null,
"personal": null,
"valid": null
},
"risk_signal_average": {
"device_risk": 0.2348,
"proxy": 0.0004,
"vpn": 0,
"tor": 0,
"spoofed_ip": 0,
"datacenter": 0,
"recent_fraud_ip": 0,
"impossible_travel": 0,
"device_network_mismatch": 0.0001
}
}
```
### Risk Signal Average
The `risk_signal_average` field represents an intelligent time-decay based algorithm of the session scores. This means:
* Recent sessions have more weight in the calculation than older sessions
* The algorithm intelligently aggregates risk signals across all sessions
* It provides a more accurate representation of current risk than a simple average
* It helps identify patterns of risk that persist across multiple sessions
## API Access and Rules
Both account and session data is available via the API in real time. This allows you to:
* Make real-time decisions based on current risk scores
* Apply different rules to accounts vs sessions
* Implement custom logic based on your specific use case
Rules can be configured to run on both Accounts and Sessions, allowing for flexible fraud prevention strategies.
## Combining Account and Session Signals
Verisoul's power comes from analyzing both account and session signals together
### New User Signup
During signup, a user will only have one session, and that session's risk score will effectively be their account score. This initial assessment is critical for preventing fraudulent accounts from being created.
### Established Accounts with Risky Sessions
An account in good standing can occasionally have a risky session that doesn't necessarily mean the entire account should be banned. For example:
* A legitimate user might connect through a VPN or proxy for privacy reasons
* A shared device might show unusual behavioral patterns
* Network conditions might trigger certain risk signals
In these cases, you might choose to:
* Block the specific session activity without banning the account
* Require additional authentication for that session
* Limit functionality until the risk is cleared
This approach allows you to maintain security while minimizing disruption for legitimate users who occasionally trigger risk signals.
## Implementation Best Practices
### When to Focus on Account Signals
* During user registration
* When users claim promotions or bonuses
* For high-value transactions
* When monitoring for multi-accounting
### When to Focus on Session Signals
* During login attempts
* When users change account settings
* For password reset requests
* When monitoring for account takeovers
### Balancing Security and User Experience
* Apply stricter controls for high-risk accounts or sessions
* Use progressive security measures based on risk levels
* Consider the context of the action being performed
* Monitor false positive rates and adjust thresholds accordingly
# Bot
Source: https://docs.verisoul.ai/signals-scores/bot
Understanding bot detection in Verisoul
Bots are automated scripts or programs that interact with your application, often for malicious purposes. Verisoul's bot detection system identifies and prevents automated attacks while allowing legitimate users to access your services.
## Bot Detection Across Platforms
Verisoul's bot detection works across both web applications and native mobile apps through our SDKs. Our approach adapts to the specific characteristics and capabilities of each platform while maintaining consistent protection against automated threats.
## Bot Detection Signals
Verisoul uses multiple signals to identify bot activity:
### Environment Analysis
* **Automation framework detection**: Identifying tools like WebDriver, Appium, or other automation frameworks
* **Emulation detection**: Detecting when applications run in simulated or headless environments
* **Runtime inconsistencies**: Identifying mismatches in reported capabilities and actual behavior
* **Execution anomalies**: Unusual patterns in how code executes or resources are accessed
* **Rendering analysis**: Identifying non-human patterns in UI rendering and display
* **Native API interactions**: Detecting unnatural patterns in how native APIs are called (in mobile apps)
* **System integrity**: Checking for signs of tampering with the runtime environment
### Behavioral Analysis
* **Mouse movements**: Lack of natural cursor movements or unnatural patterns
* Our models analyze hundreds of mouse events per second to detect subtle differences between human and automated movements
* Similar to how CGI animation can look "not quite right" to human observers, our models can detect the subtle differences in how bots move cursors
* **Touch gestures**: Analyzing touch patterns on mobile devices for natural variability
* **Accelerometer data**: Detecting unnatural device movement patterns in mobile apps
* **Keyboard patterns**: Unnatural typing rhythms, speeds, or consistency
* Humans have variable typing speeds, occasional errors, and natural pauses
* Bots often type with mechanical precision or unnatural timing
* **Clipboard usage**: Identifying abnormal clipboard interactions
* **Navigation timing**: Unusually fast or consistent page transitions
* **Interaction patterns**: Missing hover events or natural pauses
### Network Patterns
* **Request timing**: Unnaturally consistent intervals between requests
* **Parallel connections**: Abnormal connection patterns
* **Header consistency**: Unusual or inconsistent HTTP headers
* **IP reputation**: Known bot hosting providers or proxy services
* **Traffic patterns**: Unusual traffic spikes or patterns
## Relationship with Other Risk Signals
It's important to understand that bot detection is distinct from, but related to, other risk signals:
### Bot vs. Device Risk
A high bot score does not necessarily mean a high device risk score, and vice versa:
* A legitimate user on a suspicious device (e.g., jailbroken phone, unusual browser configuration) may have a high device risk but low bot score
* A sophisticated bot running on a standard personal computer may have a low device risk but high bot score
Our bot detection models incorporate device signals but focus specifically on identifying automated behavior rather than just unusual device characteristics.
### Bot vs. Network Risk
Similarly, network risk signals (VPN, proxy, datacenter) do not automatically indicate bot activity:
* Many legitimate users employ VPNs for privacy or accessing content
* A bot may operate from a clean residential IP with no network risk signals
Our bot detection considers network context but primarily focuses on behavioral and environmental signals that indicate automation.
## Bot Risk Score
The Bot Risk Score (0-100) indicates the likelihood that a session is automated rather than human. Higher scores indicate higher likelihood of bot activity.
```json theme={null}
{
"account_score": 0.9401,
"bot": 0.9709, // High bot risk
}
```
In this example, the high bot score is influenced by the detected headless browser, WebDriver presence, and lack of natural user interactions.
# Device
Source: https://docs.verisoul.ai/signals-scores/device
Understanding device data and risk scores in Verisoul fraud prevention
Device data provides critical information about the hardware and software environment a user is using, helping to identify suspicious activities and potential fraud.
## Device Data
Verisoul collects various device signals to create a comprehensive profile of the user's environment. This data helps in identifying legitimate users and detecting potentially fraudulent activities.
### Example Device Data
```json theme={null}
{
"browser": {
"type": "Chrome",
"version": "132.0.0.0",
"language": "en",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
"timezone": "America/Chicago"
},
"device": {
"category": "desktop",
"type": "Mac",
"os": "macOS 10.15.7",
"cpu_cores": 16,
"memory": 8,
"gpu": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4 Max, Unspecified Version)"
}
}
```
### Device Categories
The `device.category` field indicates the type of device used in the session. In practice, 99%+ of traffic will be `desktop`, `mobile`, and `tablet`. The rest are edge cases from the user agent parser.
| Category | Description | Examples |
| - | - | - |
| `desktop` | Windows, Mac, and Linux computers | MacBook, Windows PC, Chromebook |
| `mobile` | Phones | iPhone, Android phones |
| `tablet` | Tablets | iPad, Android tablets |
| `console` | Gaming console browsers | PlayStation, Xbox, Nintendo |
| `smarttv` | Smart TV browsers | Samsung TV, LG webOS, Roku |
| `wearable` | Smartwatch browsers | Apple Watch, Wear OS |
| `embedded` | IoT and kiosk devices | Kiosks, embedded browsers |
| `other` | Anything that doesn't match the above | — |
### Data Collection Limitations
It's important to note that the device information collected is not guaranteed to be complete or accurate due to several limitations:
* **Privacy-preserving browsers**: Browsers like Brave, Firefox, and Safari may intentionally mask or limit access to certain device characteristics.
* **Memory reporting**: Some browsers restrict access to accurate memory information.
* **User agent spoofing**: Users can modify their user agent strings to appear as different browsers or devices.
* **Hardware access restrictions**: Browser security measures may limit access to detailed hardware information.
* **OS-level restrictions**: Operating systems may prevent access to certain device characteristics.
These limitations are part of the evolving privacy landscape in web browsers and should be considered when analyzing device data.
## Device Risk Score
The device risk score is a machine learning model output that evaluates how likely a device is to be associated with fraudulent activity.
### Example Device Risk Score
```json theme={null}
{
"device_risk": 0.0295
}
```
### Understanding Device Risk
The device risk score is triggered when characteristics about the device deviate from patterns seen in normal devices. Higher scores indicate greater likelihood of suspicious activity.
Factors that can influence the device risk score include:
* **Browser automation**: Signs that the browser is being controlled programmatically
* **Bot activity**: Patterns consistent with automated bot behavior
* **Emulators**: Evidence that the device is an emulated environment rather than physical hardware
* **Virtual machines**: Indicators that the session is running in a virtualized environment
* **Inconsistent device signals**: Contradictory or impossible combinations of device characteristics
* **Tampering**: Signs that device fingerprinting is being actively circumvented
# Email
Source: https://docs.verisoul.ai/signals-scores/email
Understanding email intelligence in Verisoul
## Overview
Looking for the standalone Email Intelligence API? See [Email Intelligence API](/email-intelligence/overview) for integration, webhooks, batch support, and identity intelligence.
The `email_score` is computed by analyzing various characteristics of both the email address and its associated domain. We run comprehensive checks on the domain to understand its trustworthiness and combine that with detailed analysis of the email structure and history to create a single score.
The scoring process involves:
1. **Domain Intelligence** - Analyzing the domain's web presence, business metadata, and reputation
2. **Email Intelligence** - Examining email structure, deliverability, and breach history
3. **Signal Aggregation** - Combining all signals into a unified risk assessment
We return this as `email_score` in our API, where the score ranges from **-1 to 1**:
* **-1** represents the most trustworthy emails
* **1** represents the highest risk emails
### Score Examples
**[johndoe@gmail.com](mailto:johndoe@gmail.com)**
**Trust Signals**: email\_age\_greater\_than\_5\_years, email\_known\_online\_history
**[john@verisoul.ai](mailto:john@verisoul.ai)**
No particular risk or trust indicators detected, representing a neutral assessment.
**[confidentialbot+1232384uu8734587@tempmail.com](mailto:confidentialbot+1232384uu8734587@tempmail.com)**
**Risk Signals**: email\_alias, email\_likely\_generated, email\_high\_number\_count, domain\_disposable\_type
## Domain Intelligence
We evaluate how the domain appears in search engines and assess its overall online footprint and reputation.
Our systems analyze the domain's landing page content, design quality, and professional appearance indicators.
For business domains, we cross-reference public company data including employee count, registration details, and legitimacy markers.
We examine the domain's technical setup, including MX record configuration, DNS health, and email delivery infrastructure.
### Domain Account Velocity
The `num_account_from_domain` field helps you detect and block high-velocity domains that may be used for signup abuse or free trial fraud. This field counts how many accounts with the **same apex domain** as the current account have been seen in your project.
**Real-Time Calculation**: This count is calculated in real-time on every API call, so it always reflects the most up-to-date value.
**Key Details:**
* **Apex Domain Matching**: The count is performed at the apex domain level, meaning subdomains are included. For example, `user@mail.company.com` and `user@company.com` would both count toward the `company.com` apex domain.
* **Project Scoped**: The count only includes accounts within your specific project.
* **Capped at 1000**: If the returned value is 1000, you can assume there are *at least* 1000 accounts from that domain in your project. The count is capped for performance reasons.
* **Always Available**: This field is returned on every API call, even before the full email intelligence scoring is complete.
**Recommended Rule:**
Use `num_account_from_domain` in combination with `domain_type` to identify suspicious signup patterns:
* If `num_account_from_domain` > X **and** `domain_type` not in `{personal, relay}` → consider blocking or requiring additional verification
* Set X to a threshold reasonable for your user base (e.g., 5-10 for most B2B applications)
## Email Intelligence
Our comprehensive email analysis examines multiple dimensions of the email address to determine authenticity and risk:
**Username Pattern Recognition**: We analyze the composition of email usernames, including the frequency of periods, numbers, and numeric blocks that may indicate automated generation or suspicious patterns.
**Format Validation**: Our systems detect unusual character combinations, suspicious formatting patterns, and other indicators that suggest machine-generated or fraudulent email addresses.
**Data Breach Intelligence**: We maintain an extensive database of known data breaches and check whether the email address has appeared in previous security incidents.
**Risk Assessment**: Each breach is categorized by severity and type, allowing us to assess the overall risk profile associated with the email's exposure history.
**Real-time Validation**: We perform live checks to verify whether the email address can actually receive messages and is actively monitored.
**Mailbox Health**: Our system monitors for delivery issues, full mailboxes, and other factors that might affect email deliverability and user engagement.
## Timing
For the most accurate email score, the scoring runs asynchronously. For non-personal emails (business, education, relays, governments, etc.), if Verisoul has seen the domain before, you can expect `email_score` to be complete and included in the overall score on the first request.
For personal domains or any domains new to Verisoul, the `email_score` will be updated asynchronously and account(s) will be automatically repredicted for you.
You can determine the score is complete when `email_score` is non-null, which typically takes about 3–5 seconds after we first observe the email.
## Signals
Our email intelligence generates various trust and risk signals that contribute to the overall email score. These signals are categorized into risk indicators and trust indicators to provide a comprehensive assessment.
The **Identity Intelligence** column marks signals returned only by the standalone [Email Intelligence API](/email-intelligence/overview) when [Identity Intelligence](/email-intelligence/identity-intelligence) is enabled (`identity_intelligence: true`). All other signals are returned by both the real-time `/session/authenticate` call and the Email Intelligence API. Identity match/mismatch signals also require the relevant claim (name, phone, or country) to be supplied.
### Risk Signals
| Flag | Description | Identity Intelligence |
| :- | :- | :-: |
| email\_invalid | email is not a valid email formatted string | |
| email\_high\_number\_count | email has more than 5 numbers | |
| email\_high\_period\_count | email has more than 2 periods | |
| email\_high\_number\_numeric\_blocks | email has more than 1 distinct block of numbers | |
| email\_business\_with\_numbers | email is a business email with numbers in username | |
| email\_suspicious\_keyword | email username contains suspicious words like "stealth" or "fraud" | |
| email\_alias | email is an alias | |
| email\_role\_keyword | email is an abstract role entity (hr, sales, etc.) | |
| email\_no\_reply | email is a noreply username | |
| email\_not\_deliverable | email can't receive emails | |
| email\_likely\_generated | email appears to be illegible and randomly generated | |
| email\_young\_age | email is less than 1 year old | |
| email\_no\_online\_history | email has no breaches | |
| email\_unknown\_age | email has no breaches | |
| email\_too\_many\_breaches | email has been seen in over 50 breaches | |
| email\_risky\_online\_history | email has been seen in a high risk breach | |
| email\_mailbox\_full | email exists and is theoretically deliverable but mailbox is full currently | |
| domain\_relay\_type | domain is a known relay domain | |
| domain\_disposable\_type | domain is a known disposable | |
| domain\_low\_trust\_business | domain is a low trust business | |
| domain\_no\_mx\_record | domain has no mx record | |
| domain\_suspicious\_keywords | domain contains suspicious keywords like "temp" or "throwaway" | |
| domain\_whois\_less\_than\_1\_year | domain was registered less than 1 year ago | |
| domain\_risky\_personal | domain is known to be risky relative to gmail.com | |
| domain\_does\_not\_resolve | domain does not resolve to an IP | |
| domain\_invalid | domain (fqdn) cannot receive mail: no MX record and no A/AAAA address record (or the domain explicitly opts out of mail via a null MX record) | |
| no\_online\_presence | no independent online accounts found (the email provider's own platform, e.g. Google for a gmail address, does not count) | ✓ |
| sparse\_online\_presence | only 1-2 independent online accounts found | ✓ |
| identity\_name\_mismatch | input name does not match name found in external data | ✓ |
| identity\_phone\_mismatch | input phone does not match phone found in external data | ✓ |
| identity\_country\_phone\_mismatch | phone country does not match expected country | ✓ |
| identity\_too\_many\_phones | velocity abuse indicator | ✓ |
| domain\_country\_mismatch | domain TLD does not match expected country | ✓ |
### Trust Signals
| Flag | Description | Identity Intelligence |
| :- | :- | :-: |
| email\_age\_greater\_than\_3\_years | email first seen more 3 years ago | |
| email\_age\_greater\_than\_5\_years | email first seen more 5 years ago | |
| email\_age\_greater\_than\_10\_years | email first seen more 10 years ago | |
| email\_age\_greater\_than\_15\_years | email first seen more 15 years ago | |
| email\_known\_online\_history | email has history in data breaches | |
| email\_trusted\_online\_history | email has been seen in high trust breaches | |
| domain\_trusted\_type | domain is a government or education type | |
| domain\_trusted\_personal | domain is known higher trust relative to gmail.com | |
| domain\_high\_trust\_business | domain is a known business | |
| domain\_high\_trust\_email\_provider | domain provider is a known enterprise provider | |
| domain\_whois\_greater\_than\_5\_years | domain first registered over 5 years ago | |
| domain\_trusted\_relay | domain is a Apple Private Relay, a trusted forwarding service | |
| moderate\_online\_presence | 3-5 independent online accounts found | ✓ |
| strong\_online\_presence | more than 5 independent online accounts found | ✓ |
| identity\_name\_match | input name matches external data | ✓ |
| identity\_phone\_match | input phone matches external data | ✓ |
| domain\_country\_match | domain TLD matches expected country | ✓ |
## Domain Types
Our system categorizes email domains into various types based on their purpose and characteristics:
| Type | Description |
| :- | :- |
| relay | a email forwarding service |
| personal | a public email service |
| disposable | a throwaway or temporary email provider |
| business | a business domain |
| government | a government entity domain |
| education | an educational entity domain |
| invalid | a domain that doesn't have a valid mx server attached |
| not\_active | a domain that appears to be inactive or not used |
# Expected Countries
Source: https://docs.verisoul.ai/signals-scores/expected-countries
Declare where an account should legitimately operate from and detect sessions that fall outside it
Expected countries let you tell Verisoul where an account should legitimately operate from. Once declared, every session for that account is checked against the declaration. Out-of-list activity surfaces in two ways: as the `outside_expected_countries` risk signal, and as a contribution to the [location spoofing score](/signals-scores/location#location-spoofing-score).
This is useful whenever you know something about a user's legitimate geography that Verisoul lacks on its own: the countries a customer's license covers, or the home country a user registered with.
## Declaring expected countries
Expected countries are declared per account through the `expected_countries` field on the [Account object](/api-reference/types/account), as a list of [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes. You can send it anywhere the Account object is accepted:
* **`POST /session/authenticate`**, alongside the rest of the account payload
* **`PUT /account/{account_id}`**, the [account update endpoint](/api-reference/account/update)
```json theme={null}
{
"account": {
"id": "acc_123456789",
"email": "user@example.com",
"expected_countries": ["US", "CA"]
},
"session_id": "00000000-0000-0000-0000-000000000001"
}
```
Sending a non-empty list replaces the stored declaration. Omitting the field or sending an empty list leaves it unchanged. See the [Account type](/api-reference/types/account#expected-countries) for full update semantics.
## How it surfaces
Declaring expected countries affects two outputs on every subsequent session for the account: a risk signal and a score.
### The `outside_expected_countries` risk signal
Sessions whose observed country falls outside the declaration carry the `outside_expected_countries` signal in [`risk_signal_details.network`](/signals-scores/risk-signals). The signal is deterministic, which makes it a good target for hard allow and block rules:
* It fires when an observed session country, either the IP country or the [resolved true country](/signals-scores/location#true-country-detection), is outside the declared list
* It fires only for accounts that declared expected countries, and only on definitive evidence: sessions with missing or unresolvable geo data are skipped
* It is independent of the proxy and VPN score gates, so it appears even on sessions whose network scores otherwise read clean
### The location spoofing score
When a session's location falls outside every declared country, the distance from the session's location to the nearest declared country feeds the session's [`location_spoofing` score](/signals-scores/location#location-spoofing-score). The contribution is dynamic and scales with the severity of the distance: a session just across a border from a declared country, say a US-declared account appearing in Mexico, raises the score modestly, while the same account appearing in China raises it substantially.
The component contributes only when the account declared expected countries, the session sits outside all of them, and location data for the session is available. This covers the case where the user really is where their IP says they are, with zero proxy or VPN involvement, but that place is outside everywhere the account should legitimately be.
# Impossible Travel
Source: https://docs.verisoul.ai/signals-scores/impossible-travel
Understanding impossible travel detection in Verisoul fraud prevention
Impossible travel detection is a sophisticated fraud prevention technique that identifies physically impossible location changes within a short timeframe, helping to detect account takeovers and shared accounts.
## What is Impossible Travel?
Impossible travel occurs when a user appears to move between geographic locations at a speed that would be physically impossible. For example, if a user logs in from New York and then, 30 minutes later, logs in from Tokyo, this would be flagged as impossible travel since no commercial flight could cover that distance in such a short time.
## How Impossible Travel Detection Works
Verisoul's impossible travel detection system works through several key mechanisms:
### Location Tracking
* **IP geolocation**: Determining the geographic location of each session
* **Historical location data**: Maintaining a record of previous access locations across all past sessions for an account
* **Time-stamped access logs**: Recording when each access occurred
### Travel Speed Calculation
* **Distance calculation**: Computing the physical distance between consecutive access points across all historical sessions
* **Time differential**: Measuring the elapsed time between access events
* **Speed determination**: Calculating the implied travel speed between locations
## Scoring System
The impossible travel score is calculated by analyzing:
* **Travel speed**: The calculated speed between consecutive logins
* **Distance traveled**: The physical distance between locations
* **Geolocation confidence**: Adjustments for known imprecision in IP geolocation data
* **Historical patterns**: Comparison with the user's established location patterns
Our scoring algorithm is designed to minimize false positives by:
* Applying confidence thresholds to IP geolocation data
* Considering the margin of error in geolocation precision
* Using adaptive thresholds based on regional IP mapping accuracy
* Incorporating machine learning to improve detection accuracy over time
## Risk Indicators
Several patterns in impossible travel detection can indicate different types of fraud:
### High-Risk Patterns
* **Intercontinental jumps**: Rapid movement between continents
* **Oscillating locations**: Repeatedly bouncing between distant locations
* **Consistent impossible patterns**: Regular occurrence of impossible travel
* **Correlation with other risk factors**: Impossible travel combined with other suspicious signals
### Common Fraud Scenarios
* **Account sharing**: Multiple users accessing the same account from different locations
* **Account takeover**: Unauthorized access from a location different from the legitimate user
* **Bot Activity**: Automated scripts accessing accounts from distributed networks or cloud infrastructure
* **VPN/proxy hopping**: Rapidly switching between different VPN exit nodes
# Location
Source: https://docs.verisoul.ai/signals-scores/location
Understanding location signals in Verisoul fraud prevention
Location data provides critical signals for fraud detection. Verisoul analyzes user location information to identify suspicious patterns and help protect your business from geographically-based fraud threats.
## Location Signal Types
Verisoul collects and analyzes various types of location data:
### IP Geolocation
* **Country**: The country associated with the IP address
* **Region/State**: The regional subdivision within the country
* **City**: The city associated with the IP address
* **Coordinates**: Latitude and longitude
* **Postal code**: Where available
### Country Tracking
Verisoul tracks country information at both the session and account level:
* **Session country**: The country detected for the current session based on IP geolocation
* **Account country**: The most recent country associated with the user account
* **Account countries**: A historical record of all countries a user has been seen in
### True Country Detection
Verisoul employs advanced techniques to detect a user's actual location, even when they're using proxies or VPNs:
* **true\_country\_code**: Represents our best determination of a user's actual location
* Available for approximately 10% of proxy/VPN traffic
* Leverages multiple data providers and proprietary ML algorithms
* Helps identify users attempting to mask their true location
* Defaults to the IP address country (session.location.country\_code) when no additional signals are found
## Location Spoofing Score
The `location_spoofing` score measures intent: is this user trying to appear somewhere other than where they actually are? The score separates that question from the technical means used. A VPN, a proxy, or an odd timezone is a mechanism, and plenty of legitimate users run VPNs while their location spoofing score stays low. The score rises when independent evidence shows the user's apparent location and actual location disagree, and it rises more when the gap is large: a user appearing one border away from their evidence, Mexico versus the US, scores lower than a user masking an intercontinental move like China to the US.
### What feeds the score
The score blends several independent location-consistency checks, each comparing a different view of the user's location:
* **Timezone vs IP geography**: the device's configured timezone is geographically far from where the IP says the user is
* **Device language vs IP country**: the device's language implies a country inconsistent with the IP location; common variants like `en-US` abroad are treated as benign
* **Network latency**: connection round-trip timing is inconsistent with the claimed location, the pattern of traffic tunneled through a distant intermediate server
* **WebRTC and GPS evidence**: peer-connection or device-location data disagrees with the IP-derived location
* **Distance from [expected countries](/signals-scores/expected-countries)**: for accounts that declared expected countries, how far the session's location is from the nearest declared country
A session with one mild inconsistency typically stays low. Several independent signals pointing to the same large displacement drive the score up.
## Location Data in API Response
The Verisoul API provides comprehensive location data in the response:
```json theme={null}
{
"session": {
"true_country_code": "US",
"location": {
"continent": "NA",
"country_code": "US",
"state": "Texas",
"city": "Austin",
"zip_code": "78703",
"timezone": "America/Chicago",
"latitude": 30.2881,
"longitude": -97.764
}
},
"account": {
"country": "US",
"countries": ["US"]
}
}
```
### Understanding Location Fields
* **session.true\_country\_code**: Our determination of the user's actual country, even if they're using a proxy/VPN
* **session.location**: Detailed location information derived from IP geolocation
* **account.country**: The most recent country associated with this user account
* **account.countries**: Array of all distinct countries this user has been seen in across all sessions (include true\_country\_code)
> **Note**: When no proxy/VPN is detected or when we cannot determine the true location behind a proxy, `true_country_code` will default to the same value as `session.location.country_code`.
## How to use location signals
Location signals provide powerful tools for fraud prevention and regulatory compliance. Here are the most common ways our customers leverage location data:
### Blocking Risky Geographies
One of the most common rules implemented by companies is blocking access from high-risk geographies or regions they don't service:
* **Regulatory Compliance**: Block users from countries where your service is not licensed to operate
* **Fraud Prevention**: Restrict access from countries with historically high fraud rates
* **Risk Management**: Apply additional verification steps for users from moderate-risk countries
### Leveraging True Country Detection
The `true_country_code` field is particularly valuable for geographic blocking rules:
* **Bypass Prevention**: Detect users attempting to bypass geo-restrictions using VPNs or proxies
* **Accurate Enforcement**: Apply geographic rules based on the user's actual location, not their apparent IP location
* **Compliance Assurance**: Ensure regulatory compliance even when users attempt to mask their location
## Implementation in Verisoul
### Multi-Provider IP Intelligence
Verisoul uses multiple IP intelligence providers to maximize accuracy:
* Combines data from several leading IP intelligence services that is updated in real-time
* IP geolocation is one of the few data sources that Verisoul does not derive entirely on its own, due to the massive scale required to cover global IP locations
* Maintains integrations with multiple specialized IP intelligence providers to ensure comprehensive global coverage
* Applies proprietary ML algorithms to determine the most accurate location
* Our ML system selects the most accurate provider for any given IP based on:
* Historical accuracy evaluations by geography
* Real-time signals such as which of our 300+ cloud regions the request was routed to
* Request latencies and network path analysis
* Consistency checks across multiple data sources
* Identifies discrepancies that may indicate location spoofing
* Attempts to detect true location even when proxies/VPNs are in use
# Mobile
Source: https://docs.verisoul.ai/signals-scores/mobile
Understanding native mobile device data in Verisoul fraud prevention
Sessions collected through the native Verisoul iOS or Android SDK include a `mobile` object with device facts read directly from the operating system — values that are more precise than what a browser or WebView can report.
The `mobile` object is returned in the `session` block of `POST /session/authenticate`, `POST /session/unauthenticated`, and `GET /session/{session_id}`.
## Mobile Data
### Example Mobile Data
```json theme={null}
{
"mobile": {
"brand": "Apple",
"model": "iPhone",
"system_memory_gb": 8,
"boot_count": null,
"system_uptime_days": 2.0,
"battery_level": 0.82,
"languages": ["en-US"],
"oem_app_integrity": "unavailable",
"oem_device_integrity": "unavailable"
}
}
```
Sessions that did not come from the native mobile SDK (web and WebView-only sessions) return an empty object:
```json theme={null}
{
"mobile": {}
}
```
To check which SDK produced a session, use the top-level `source` field (`web`, `ios`, or `android`) and its companion `source_version` (the native SDK version, null for web) rather than inspecting the mobile block.
### Fields
| Field | Description |
| - | - |
| `brand` | Device brand as reported by the operating system (e.g. `Apple`, `samsung`, `google`) |
| `model` | Device model as reported by the operating system (e.g. `SM-S921B`, `Pixel 6a`). iOS reports the generic device family (`iPhone`, `iPad`) |
| `system_memory_gb` | Total device RAM in GB |
| `boot_count` | Number of times the device has been booted. Very low boot counts can indicate factory-reset devices common in device farms |
| `system_uptime_days` | Days since the device last booted |
| `battery_level` | Battery charge level between 0 and 1 |
| `languages` | Device language preferences (e.g. `["en-US"]`) |
| `oem_app_integrity` | Verdict from the platform's app attestation (Play Integrity app recognition and licensing on Android; App Attest on iOS): `passed`, `failed`, or `unavailable` |
| `oem_device_integrity` | Verdict from the platform's device attestation (Play Integrity device recognition on Android; DeviceCheck on iOS): `passed`, `failed`, or `unavailable` |
### Platform availability
Not every field is collected on every platform. Fields the platform does not provide are `null`:
| Field | iOS | Android |
| - | - | - |
| `brand`, `model`, `system_uptime_days`, `languages` | ✓ | ✓ |
| `system_memory_gb` | ✓ | `null` |
| `battery_level` | ✓ | `null` |
| `boot_count` | `null` (not exposed by iOS) | ✓ |
`oem_app_integrity` and `oem_device_integrity` return `unavailable` when the platform attestation was not performed or could not be validated for the session, for example when the app has not integrated App Attest or the project has no DeviceCheck configuration on iOS. iOS sessions commonly read `unavailable` for both verdicts today. Android sessions carry Play Integrity verdicts on nearly all traffic. A Play license verdict of `UNEVALUATED` (the user is signed out of a Play account, or the app was installed outside the Play Store) reads `failed` and feeds `android_app_integrity_failed`. Projects that distribute outside the Play Store, such as carrier preloads, are configured so this case reads `unavailable` and carries no risk.
## How mobile data relates to risk
The `mobile` object contains device facts only — the same way the `browser` and `device` objects report facts. All mobile risk interpretation (emulators, rooted or jailbroken devices, factory-reset boot profiles, integrity failures) is emitted through the `device` array of [Risk Signal Details](/signals-scores/risk-signals), so there is exactly one catalog of named risk signals.
Native sessions also improve existing `device` fields: `device.os` is sourced from native readings on both platforms, and on iOS `device.gpu`, `device.cpu_cores`, and `device.memory` are as well — fixing values the WebView masks, clamps, or misreports (Android GPU, core count, and memory follow once the SDK collects them; WebView values remain the fallback).
# Multi-Accounting
Source: https://docs.verisoul.ai/signals-scores/multi-accounting
Understanding and preventing multi-accounting fraud with Verisoul
Multi-accounting (also known as duplicate accounts) occurs when a single person creates and operates multiple accounts on your platform. This is a common fraud pattern that can lead to significant losses through promotional abuse, collusion, and other fraudulent activities.
## Why Multi-Accounting Is a Problem
Claiming welcome bonuses, referral rewards, or discounts multiple times
Unfair advantages in gaming, gambling, or marketplace platforms
Creating artificial engagement or fake reviews
Consuming limited resources or free trials repeatedly
## Components of Verisoul's Multi-Accounting Detection
Verisoul's multi-accounting detection system consists of several interconnected components that work together to provide an accurate risk assessment.
Verisoul identifies the following match types between accounts:
The only deterministic match type with zero false positives
Always results in a score of 1 (highest confidence)
Based on browser-specific identifiers
Based on hardware and software characteristics
Includes device fingerprinting techniques
Probabilistic with varying confidence levels
Analysis of network patterns and characteristics
Includes IP address analysis and packet inspection
Probabilistic with varying confidence levels
Analyzes string similarity of normalized email addresses
Strips special characters like dots (.) and plus (+) signs
Identifies users creating variations of the same email
Exact match on the normalized E.164 phone number passed in the account object
Full-strength match, like browser and email
Requires the phone field on authenticate
Exact match on the normalized username passed in the account object
Full-strength match — catches obvious handle reuse across accounts
Requires the username field on authenticate
Exact match on the normalized name (lowercased, punctuation and diacritics stripped), built from the name parts provided
Moderate-strength match — a name match alone never produces a full-strength link, but name plus another match (device, network, email) does
Requires first\_name and/or last\_name on authenticate
Each potential connection between two accounts receives a score (0-1) based on several factors:
Match type strength: Browser, email, phone, and username matches (full strength) > Name matches (moderate strength) > Device matches > Network matches
Number of sessions matched: More matching sessions increase confidence
Fingerprint uniqueness:
- Globally unique fingerprints provide higher confidence
- Common fingerprints (seen frequently across many users) provide lower confidence
Time between sessions:
- Sessions close together in time provide stronger signals
- Sessions weeks apart provide weaker signals
Two accounts sharing a unique device fingerprint across multiple sessions within minutes would receive a high account-to-account score, while two accounts sharing only a common network characteristic weeks apart would receive a low score.
The `multiple_accounts` field is the culmination of all account-to-account scores, representing the overall likelihood that an account is part of a multi-accounting scheme:
Range: 0 (no risk) to 1 (highest risk)
Primary risk indicator: This is the most important field for assessing multi-accounting risk
This probability is calculated by:
- Evaluating all account-to-account scores
- Considering the number of linked accounts
- Applying the accounts linked threshold configuration
## Accounts Linked Threshold
The accounts linked threshold is a configurable parameter that affects how the `multiple_accounts` field is calculated:
Default value: 1 (a single perfect match results in probability of 1)
Custom values: Can be adjusted based on your business needs
Example: Setting to 3 allows users to have up to 3 accounts before triggering high probability scores
Purpose: Helps reduce false positives while still preventing scaled fraud
Contact the Verisoul team to configure the accounts\_linked\_threshold for your specific use case.
## Accounts Linked API Response
When you query the Verisoul API for accounts linked to a specific account, you'll receive data about potential multi-accounting:
```json theme={null}
{
"accounts_linked": [
{
"account_id": "account_1",
"score": 1, // Certain match
"match_type": [
"browser",
"network",
"device",
"phone",
"username"
],
"email": "account_1@example.com",
"phone": "+14155550123",
"first_name": "Jane",
"last_name": "Doe",
"username": "jane.doe",
"expected_countries": ["US", "CA"],
"first_seen": "2025-06-10T16:47:29.661Z",
"last_seen": "2025-12-03T08:21:14.902Z",
"lists": [
"allow"
],
"metadata": {}
},
{
"account_id": "account_2",
"score": 0.3, // Low confidence match
"match_type": [
"network"
],
"email": "account_2@example.com",
"first_seen": "2025-08-21T09:12:04.118Z",
"last_seen": "2025-12-15T19:45:52.330Z",
"lists": [
"block"
],
"metadata": {}
}
]
}
```
## How Scores Roll Up Into Multi-Accounting Probability
The relationship between account-to-account scores and multiple accounts score:
1. Strong matches dominate: A single strong match (especially a browser match with score 1) can result in a high probability
2. Quantity vs. quality: Many weak matches may not significantly increase probability
3. Threshold application: The accounts\_linked\_threshold determines how many strong matches are needed for a high probability
### Risk Examples
An account with one perfect browser match (score 1) would likely have a high `multiple_accounts` score (near 1)
An account with several medium-strength device matches (scores 0.4-0.6) might have a moderate `multiple_accounts` score
An account with many low-strength network matches (scores 0.1-0.3) would likely have a low `multiple_accounts` score
## Why Fingerprints Aren't Returned
1. Fraud prevention effectiveness: Raw fingerprints are easily changed by fraudsters who modify device attributes
2. Stability issues: Fingerprints aren't exact, can have false positives, and aren't always stable
3. Complexity: Resolving and actioning a graph database is complex
4. Automation: Verisoul automates this process while remaining flexible enough to tune to your ecosystem
## Viewing Account Linkages
The graph of linked accounts is viewable in the Verisoul dashboard and can be retrieved at any time via the accounts linked API. This provides a visual representation of potential multi-accounting clusters.
# Network
Source: https://docs.verisoul.ai/signals-scores/network
Understanding network data and risk signals in Verisoul fraud prevention
Network data and risk signals provide critical information about how users connect to your application, helping to identify suspicious activities and potential fraud.
## Network Data
Network data provides fundamental information about a user's connection to your application. Here's an example of the network data collected:
```json theme={null}
{
"network": {
"ip_address": "2600:1700:261:b810:4d06:c3c4:86e:ffc",
"service_provider": "AT&T Internet",
"connection_type": "isp",
"asn": 7018,
"subnet": "107.209.253.0/24"
}
}
```
### IP Addresses
Verisoul collects IP address information from user sessions. We prioritize collecting IPv6 addresses when available, followed by IPv4. In some cases, multiple IP addresses (both IPv6 and IPv4) may be collected from a single session. This can occur when:
* A device switches between networks during a session
* Different traffic is being routed through different networks
* Network transitions occur (e.g., moving from Wi-Fi to cellular)
### Service Provider
The service provider field identifies the Internet Service Provider (ISP) or network operator responsible for the IP address. This information helps establish the legitimacy of the connection and can be used to identify patterns associated with specific providers.
### Connection Type
The connection type categorizes the network based on its primary purpose or organization. This classification provides context about the nature of the connection. Possible values include:
* **isp**: Standard residential internet service provider
* **mobile**: Mobile carrier networks
* **hosting**: Cloud service providers and hosting facilities
* **education**: Educational institution networks
* **government**: Government agency networks
* **private\_relay**: Privacy services like Apple's Private Relay
* **corporate\_vpn**: Enterprise VPN or tunnel used to access external networks
* **corporate\_bot**: Automated traffic originating from enterprise crawlers or security tooling
* **satellite**: Satellite internet provider networks
### ASN and Subnet
`asn` is the autonomous system number of the network the session came from, and `subnet` is the /24 block the [IP reputation](#ip-reputation) score was computed on. The subnet is always an IPv4 block: it comes from the IPv4 leg of the session's network collection, so it can differ from a session whose `ip_address` is IPv6. Both are null when the session has no IPv4 collection.
## Network Risk Signals
Verisoul analyzes network data to generate risk signals that can indicate potential fraud or suspicious activity. Most of these signals are generated through machine learning models, with the exception of Tor detection.
### Device-Network Mismatch
This signal indicates a discrepancy between the reported device characteristics and the network properties. Our system performs deep packet inspection to determine the device type based on networking cards used, then compares this with the reported device characteristics.
A device-network mismatch is a strong indicator of proxy or VPN usage, as it suggests the device is presenting inconsistent networking information.
### Datacenter
The datacenter signal identifies when a connection is originating from a cloud service provider or hosting facility rather than a residential or mobile network. This classification is based on the ISP information and network characteristics.
Datacenter IPs are frequently used for automated attacks and fraudulent activities, as they provide anonymity and can be provisioned quickly. This signal can occur alongside proxy or VPN signals.
### Tor
Tor detection identifies connections coming from the Tor anonymity network. Unlike our other network signals, Tor detection is primarily based on known exit node lists rather than machine learning models.
Tor connections mask a user's true location and identity by routing traffic through multiple relays, making them a common tool for users attempting to hide malicious activity.
### VPN
VPN (Virtual Private Network) detection identifies when users are routing their traffic through encrypted tunnels to mask their true location and IP address. Rather than simply checking IP addresses against known VPN provider lists, Verisoul performs 11 real-time active tests from the client side to collect data and run a machine learning model.
Our VPN detection includes:
* Deep packet inspection
* Latency analysis
* Location analysis
* Traffic pattern recognition
### Proxy
Proxy detection identifies when users are routing their traffic through intermediary servers. While similar to VPNs in some ways, proxies often lack encryption and may operate differently.
Our system distinguishes between different types of proxies:
* Open proxies
* Anonymous proxies
* Transparent proxies
* Residential proxy networks
* Mobile proxy networks
Like our VPN detection, proxy identification uses multiple real-time tests and machine learning rather than simple IP list matching.
### IP Reputation
IP reputation scores the network block behind the session's IP address on what its traffic has done across Verisoul's customer base, rather than what a vendor says the IP is. Every night we score each /24 block (and each network operator, as a fallback for thin blocks) on the share of its recent sessions that carried network or automation anomalies, weighted so that no single account can move a block on its own.
The score runs from **-1 to 1**, the same convention as `email_score`:
* `0` means a typical network, or one we have no history for
* Positive values mean the block runs riskier than typical; each +0.23 is a doubling of its anomaly rate, and +0.5 is roughly 4.5x typical
* Negative values mean the block has a cleaner history than typical, which dampens the other network signals
`risk_signal_scores.ip_reputation` carries the score and `risk_signals.ip_reputation` is true above 0.5. The score is not part of `risk_signal_average`. A block's history refreshes daily, so a block that turns hot is caught within the week.
## Implementation Notes
In some cases, the distinction between proxy and VPN can be blurred. Our system attempts to classify the connection as accurately as possible based on its characteristics, but will prioritize identifying the most likely category.
The datacenter signal can occur alongside proxy or VPN signals, as many proxy and VPN services operate from datacenter environments.
Our approach to network risk signal detection focuses on active testing and behavioral analysis rather than static lists, allowing us to identify new and evolving evasion techniques.
### Reducing False Positives
Our real-time active testing approach significantly reduces false positives compared to traditional IP-based detection methods. Static IP reputation databases can lead to false positives because:
* IP addresses frequently rotate among users
* Multiple legitimate users may share the same IP address (especially with carrier-grade NAT)
* IP addresses previously used for proxies may be reassigned to legitimate users
* VPN and proxy services regularly change their infrastructure
By performing real-time active tests from the client side rather than relying solely on IP reputation databases, we can accurately determine the current network characteristics of each session. This approach ensures that users aren't incorrectly flagged based on historical IP data, providing a more accurate and fair risk assessment.
# Risk Signal Details
Source: https://docs.verisoul.ai/signals-scores/risk-signals
Named reason codes that explain the network and device risk scores for a session
The `risk_signal_details` object gives you concrete, named reason codes that explain *why* a session's risk scores are elevated. It is returned as a sibling of `risk_signals` and `risk_signal_scores` inside the `session` object on `POST /session/authenticate`, `POST /session/unauthenticated`, and `GET /session/{session_id}`.
```json theme={null}
"risk_signal_details": {
"network": [
"true_country_mismatch",
"ip_on_vpn_list",
"ip_device_timezone_mismatch"
],
"device": []
}
```
For a clean session the arrays are empty; for a risky session they contain only the reasons that contributed to risk. Signal presence is designed to be safe to write rules against — each signal appears only when there is affirmative evidence for it.
## How signals relate to scores
Risk signals are consistent with the scores they explain: a session whose proxy and VPN scores read clean will never carry a proxy- or VPN-related signal, and a session whose device risk score reads clean will never carry a probe-based device signal. Some signals are deliberately independent of score levels because they are meaningful on their own: location-consistency signals such as `true_country_mismatch` and `outside_expected_countries`, the network registry signals (`new_asn`, `leased_network`, `network_registry_location_mismatch`, `network_operator_changed`, `network_block_holder_mismatch`), and the administrative device-brand signals `chinese_sanctioned_device` and `chinese_device`, which describe a factual property of the device rather than risk evidence and are designed to be combined with location or other conditions in rules.
As risk scores increase, sessions typically carry more corroborating signals: a moderately risky session usually shows one or two reasons, while a highly risky session shows several.
## Network Signals
Network signals explain the `proxy`, `vpn`, and location-related scores. They describe evidence that the session's network path or claimed location is not what it appears to be. The network registry signals (`new_asn` through `network_block_holder_mismatch`) describe the network the IP belongs to, from public internet-registry and routing records; they are refreshed nightly and appear regardless of the session's proxy and VPN scores.
| Risk Signal | Description |
| :- | :- |
| network\_latency\_anomaly | connection round-trip timing is anomalously high for the session's region and connection type — the classic pattern of traffic tunneled through an intermediate server |
| network\_fingerprint\_anomaly | low-level network fingerprint does not look like the browser the session claims to be |
| network\_device\_os\_mismatch | OS inferred from how the device builds its network packets disagrees with the OS the device claims — strong evidence of a proxy relaying traffic from a different machine |
| true\_country\_mismatch | the country Verisoul resolves as the user's most likely true location differs from the raw IP country |
| ip\_on\_datacenter\_list | IP is hosted in a known datacenter or cloud-hosting range rather than a residential or mobile network |
| ip\_device\_timezone\_mismatch | device's configured timezone is geographically far from the IP location |
| ip\_device\_lang\_country\_mismatch | device language implies a country inconsistent with the IP location (common benign variants like `en-US` abroad do not fire) |
| outside\_expected\_countries | an observed session country (IP or resolved true country) falls outside the account's declared [expected countries](/signals-scores/expected-countries); fires only on definitive out-of-list evidence, and only for accounts that declared expected countries |
| ip\_on\_proxy\_list | one or more IP intelligence sources flag the IP as a proxy; when both proxy and VPN list evidence exist, only the stronger of the two is emitted |
| true\_ip\_leak | a network probe observed an IP revealing a different network identity than the client IP — the user's real address leaking through their proxy or VPN |
| ip\_location\_mismatch | two independent observations of the session's IP address resolve to locations far apart — one leg of the connection went through a proxy or VPN while the other went direct |
| http1\_connection | the connection to Verisoul stayed on HTTP/1.1 instead of HTTP/2 on a non-corporate network — modern browsers negotiate HTTP/2, and forcing HTTP/1.1 hides the timing measurements that reveal proxies |
| ip\_on\_vpn\_list | one or more IP intelligence sources flag the IP as a VPN exit, or it matches a known VPN service; subject to the same either-or rule as ip\_on\_proxy\_list |
| new\_asn | the network (ASN) the IP belongs to was registered to its current holder within the last two years |
| leased\_network | the network's traffic sits on IP space that changed hands in the past year, and the address space it announces turns over month to month — the footprint of an IP-leasing operation |
| network\_registry\_location\_mismatch | the network is registered with a different regional internet registry than the one serving the country its traffic comes from |
| network\_operator\_changed | the network's registered name changed within the past year to one unrelated to the old name — often a change of operator, though a corporate rebrand to an unrelated name also qualifies |
| network\_block\_holder\_mismatch | the IP's /24 block is registered to one country but announced to the internet by a network registered to another |
| mobile\_vpn\_tunnel\_detected | the device's own network configuration shows an active VPN tunnel interface — VPN evidence observed on the device itself rather than from IP intelligence |
| mobile\_proxy\_configured | an HTTP proxy is configured in the device's operating-system network settings |
## Device Signals
Device signals explain the `device_risk` score. They describe evidence that the device or browser is tampered with, automated, virtualized, or otherwise not a normal personal device.
| Risk Signal | Description |
| :- | :- |
| browser\_tampering\_detected | browser internals appear modified — anti-detect tooling, failing integrity probes, or manipulated browser properties |
| tls\_client\_mismatch | the TLS handshake does not offer HTTP/2 the way every modern browser does — typical of scripts, anti-detect tooling, or a client other than the browser the session claims to be |
| voice\_or\_audio\_anomaly | speech, audio, or media capabilities don't match the declared OS and browser |
| gpu\_anomaly | reported graphics hardware is software-rendered, a VM driver, or otherwise inconsistent with the declared device |
| os\_claim\_inconsistent\_with\_evidence | OS the browser claims doesn't match cross-platform rendering evidence, worker behavior, or hardware-permission APIs |
| vm\_or\_emulator\_indicators | storage, screen geometry, timing, touch capabilities, or system UI details match a VM, headless browser, or emulator profile |
| uncommon\_hardware\_profile | reported CPU cores, memory, or low-level numeric behavior fall outside realistic bands for the declared device type |
| automation\_framework\_detected | direct evidence of an automation tool driving the browser (webdriver flags, devtools-protocol control, automation artifacts) |
| font\_or\_emoji\_anomaly | installed font set or emoji rendering reveals an OS different from the one the browser claims |
| environment\_probe\_anomaly | minor environment tells (pointer capabilities, timer precision, missing connection metadata) that corroborate stronger signals |
| android\_emulator\_or\_rooted | Android device is rooted, an emulator, carries a test-keys/debug build fingerprint, or exposes emulator-signature network interfaces |
| android\_device\_anomaly | device brand is inconsistent with where it is being used (e.g. a brand not sold in a region appearing on that region's carriers) |
| ios\_simulator\_or\_jailbreak | iOS device is a simulator, jailbroken, has a debugger attached, reports software-simulated location, or carries impossible-on-iOS hardware signatures |
| android\_app\_integrity\_failed | Google Play Integrity reports an app-licensing, app-recognition, or device-recognition failure, or a license verdict Google could not evaluate (signed-out Play account, install outside the Play Store) |
| ios\_hardware\_or\_version\_anomaly | reported iOS version, CPU/version compatibility, memory, core count, thermal, battery, or memory-usage pattern is inconsistent with genuine Apple hardware |
| android\_developer\_mode\_enabled | USB debugging is on or developer settings are enabled |
| android\_manual\_time\_settings | automatic time or automatic time zone is disabled on the device — a manually set clock or timezone is a common location-spoofing and device-farm tell |
| android\_clone\_or\_virtual\_app | app is running inside a clone or virtual-app container (dual-space tooling) — one physical device presenting as many |
| data\_collection\_blocked | an unusually large share of device probes returned empty or placeholder values, indicating tooling is suppressing data collection |
| android\_abnormal\_boot\_profile | boot history doesn't look like a normal personal device: factory-fresh boot counts, a boot count inconsistent with the device's age, or abnormally high boot counts |
| mobile\_device\_locale\_null | mobile device reports no locale or language configuration at all — a common device-farm and factory-image tell |
| chinese\_sanctioned\_device | device brand is an OEM banned from US networks by the FCC and restricted by carriers in Europe (Huawei, ZTE); fires regardless of score levels so it can be combined with location in rules |
| chinese\_device | device brand is a mainland-Chinese manufacturer brand with little to no US carrier presence; descriptive on its own and typically combined with location in rules |
# Scoring Overview
Source: https://docs.verisoul.ai/signals-scores/scoring-overview
Understanding how Verisoul's risk scoring system works
# Verisoul Scoring System
Verisoul's fraud detection system uses a comprehensive scoring mechanism to help you identify and prevent fraudulent activities. This page provides an overview of how our scoring system works.
## Score Range and Interpretation
All Verisoul scores follow a consistent scale:
* **Range**: 0 to 1 (or 0% to 100%)
* **Interpretation**:
* **0** = Highly trusted / legitimate user
* **1** = Highly suspicious / likely fraudulent
This consistent scale makes it easy to understand and compare different risk signals across your user base.
```json theme={null}
{
"account_score": 0.15, // 15% - Low risk
"bot": 0.87, // 87% - High risk
"multiple_accounts": 0.08, // 8% - Low risk
}
```
## Decision and Account Score
At the highest level, Verisoul provides two key outputs to simplify your decision-making process:
### Account Score
The `account_score` is the comprehensive risk assessment that combines multiple signals into a single value. It represents the overall likelihood that an interaction is fraudulent.
This score is a combination of:
* Multi-accounting signals
* Risk signals (account and session)
* Bot detection
### Decision
The `decision` field provides a string representation that maps directly from the `account_score`. This gives you an actionable recommendation based on the combined risk assessment.
The decision values are:
* `"Real"` - Low risk, trusted user
* `"Suspicious"` - Medium risk, may require additional verification
* `"Fake"` - High risk, likely fraudulent
```json theme={null}
{
"account_score": 0.82, // 82% - High risk
"decision": "Fake"
}
```
The threshold for the `decision` can be configured in the Verisoul's system to match your risk tolerance.
## Score Categories
Verisoul provides several specialized risk scores that focus on different aspects of fraud:
### Account Risk Score
Evaluates the overall risk associated with an account based on persistent identity signals and historical behavior patterns. Learn more in [Account vs Session](/signals-scores/account-vs-session).
### Session Risk Score
Assesses the risk of the current interaction based on device information, behavioral biometrics, and contextual data. Learn more in [Account vs Session](/signals-scores/account-vs-session).
### Multi-Accounting Score
Detects when a single user is creating multiple accounts, often to abuse promotions or evade restrictions. Learn more in [Multi-Accounting](/signals-scores/multi-accounting).
### Bot Score
Identifies automated scripts and bot activity attempting to interact with your application. Learn more in [Bot](/signals-scores/bot).
## Using Scores Effectively
### Threshold Setting
While Verisoul provides the `decision` field as a recommendation, you can also set your own thresholds based on your risk tolerance:
* **Conservative** (lower false positives): Block only very high scores (e.g., > 0.9)
* **Aggressive** (lower false negatives): Block moderate to high scores (e.g., > 0.7)
### Contextual Application
Consider applying different thresholds based on the context:
* Higher scrutiny for high-value transactions
* Lower friction for returning users with good history
* Stricter controls during promotional periods
### Progressive Security
Implement a tiered approach to security based on risk scores:
1. Low scores: Frictionless experience
2. Medium scores: Additional verification steps
3. High scores: Block or require manual review
## The Probabilistic Nature of Fraud Detection
It's important to understand that fraud is not deterministic but constantly evolving. Fraudsters continuously adapt their tactics to bypass detection systems. This is why Verisoul returns machine learning model scores rather than binary yes/no decisions.
These scores represent **predictions** about the likelihood of fraudulent activity based on patterns observed in the data. The probabilistic approach allows for:
* Nuanced risk assessment that captures the degree of suspicion
* Flexibility in how you apply security measures based on your risk tolerance
* Continuous improvement as new fraud patterns emerge
### The Verisoul Data Advantage
Verisoul benefits from a powerful data flywheel effect that enhances model accuracy:
* **Cross-industry insights**: By analyzing fraud patterns across multiple industries, Verisoul can identify emerging threats before they become widespread
* **Broad customer base**: Data from diverse customers creates a rich training dataset that improves model performance
* **Continuous learning**: Each new fraud attempt detected contributes to improving the system for all customers
This data advantage allows Verisoul to train models with the highest possible accuracy, staying ahead of evolving fraud tactics while minimizing false positives that could impact legitimate users.
# Authentication
Source: https://docs.verisoul.ai/verifications/face-match/integration/authentication
## Authentication
Face Match can also be used to authenticate a user with a previously registered face.
When a new account signs up, include Face Match as part of the sign-up workflow and enroll the user with the [Enroll](/api-reference/face-match/enroll) endpoint
When the account signs in (or when you want to verify user identity), make the user go through Face Match again and call the [Verify Identity](/api-reference/face-match/verify-identity) endpoint
### Use Cases
Authenticate users during the sign-in process
Verify identity before processing large transactions
Confirm user identity prior to withdrawals
# Initialize Session
Source: https://docs.verisoul.ai/verifications/face-match/integration/initialize-session
How to fetch a session token for Face Match verification
For a user to go through Face Match, you must first fetch a valid session token. This token establishes the authenticity throughout the verification process.
Session tokens are valid for **30 days** from creation. You do not have to generate one immediately before the user starts — you can create it ahead of time and deliver it asynchronously, such as in an emailed or texted verification link. The user must start and complete Face Match before the token expires.
Face Match sessions cannot be initialized for projects that have groups enabled. If your project uses groups, you will need to use a different project for Face Match verification.
## Get Session ID
Your application's server makes an API request to the [GET /liveness/session](/api-reference/face-match/session) endpoint, passing the `account_id` of the user you are verifying.
```
GET /liveness/session?account_id=
```
```javascript Node theme={null}
try {
// Create a session token via Verisoul's API
let response = await fetch(
`https://api.prod.verisoul.ai/liveness/session?account_id=${accountId}`,
{
method: 'GET',
headers: {
'x-api-key': '{VERISOUL_API_KEY}'
}
}
);
let {session_id} = await response.json();
// Return the session_id to your client application
// ...
} catch (err) {
console.error(err);
}
```
```bash cURL theme={null}
curl --location 'https://api.prod.verisoul.ai/liveness/session?account_id={YOUR_ACCOUNT_ID}' \
--header 'x-api-key: {VERISOUL_API_KEY}'
```
Use `https://api.prod.verisoul.ai` for production and `https://api.sandbox.verisoul.ai` for testing. See [Environments](/api-reference/introduction#environments).
For security, best practice is to fetch the session token from a protected server, never on the client.
## Parameters
| Parameter | Required | Description |
| - | - | - |
| `account_id` | **Strongly recommended** | Your stable identifier for the user. Links the completed session to that account. |
| `id` | **Omit** | Adding `id=true` turns this into an [ID Check](/verifications/id-check/integration/initialize-session) session. |
| `referring_session_id` | Optional | The user's current Verisoul platform session, for richer risk assessment. |
| `simulate` | Optional | Forces a specific outcome in sandbox. See [Simulate Face Match](/verifications/face-match/resources/simulate-face-match). |
| `simulate_decision`, `simulate_risk_score`, `simulate_risk_flags` | Optional | Sandbox only. With `simulate=success`, sets the decision, score and flags returned by the simulated session. See the simulate page linked above. |
### Account ID
`account_id` is your own stable identifier for the user — the same one you use elsewhere in your Verisoul integration. Pass it on every session you can.
Linking a session to an account is what turns a one-off verification into a persistent identity. When the session completes it is tied to the account automatically, which:
* **Adds the session to the 1:N deduplication pool.** The user's biometric template becomes searchable, so every later Face Match is compared against it. A future session matching this face under a *different* `account_id` is flagged with [`repeat_face`](/verifications/face-match/resources/risk-flags).
* **Creates an account on the real-time platform.** This is the same account you would create by calling [/session/authenticate](/api-reference/session/authenticate). It appears in the dashboard and accumulates [account-level signals](/signals-scores/account-vs-session) such as `multiple_accounts` and `accounts_linked`.
* **Returns account context on the verify response.** List memberships and triggered [rules](/action/rules) come back from [/verify-face](/verifications/face-match/integration/verify-and-enroll), and the decision respects your allow and block lists.
A session that never receives an `account_id` — not on the session request and not through [enrollment](#enrolling-an-account-id-later) — is never added to the deduplication pool. You still get a decision for that one verification, but no future session will ever match against its face, it does not appear as an account, and `lists` and `rules` come back as empty arrays.
**Repeat verifications of the same user are not multi-accounting.** Deduplication compares *across* `account_id` values, so an account with several Face Match sessions — retries, step-ups, sign-in [authentication](/verifications/face-match/integration/authentication), or periodic re-verification — will not raise `repeat_face` for matching itself. That flag only fires when the same face shows up under a different `account_id`. Always reuse the same `account_id` for the same user instead of generating a new one per verification.
`account_id` must be a non-empty string; an empty or malformed value returns a `400 invalid_account_id` error.
### The id Parameter
Face Match and [ID Check](/verifications/id-check/integration/initialize-session) share the same `/liveness/session` endpoint, and the `id` query parameter is the only thing that distinguishes them.
* **Without `id`** — you get a Face Match session. The user takes a selfie video only. Verify it with [/verify-face](/api-reference/face-match/verify-face).
* **With `id=true`** — you get an ID Check session, and the user is additionally asked to upload an identity document.
Only add `id=true` if you actually want the user to submit an identity document, in which case follow the [ID Check integration](/verifications/id-check/integration/initialize-session) instead.
### Referring Session ID
When using Face Match alongside Verisoul's platform, you can optionally pass a `referring_session_id` parameter in your session request.
This parameter should be the session ID from the user's current interaction with Verisoul's platform, before they begin the Face Match verification. By providing this context, Verisoul can make more informed risk assessments during the verification process.
Passing a referring session ID provides several benefits:
* Enhanced risk assessment capabilities for Face Match
* Access to referring session signals during verification
* Better detection of impersonation attempts and verification link sharing
For more details on the API parameters, see the [session endpoint](/api-reference/face-match/session#parameter-referring-session-id). To learn about the additional risk signals available when using a referring session ID, refer to the [referring session signals](/verifications/face-match/integration/response-signals#referring-session-signals).
## Enrolling an Account ID Later
Some flows run verification before the account exists — for example, verifying a user before they finish signing up. In that case, create the session without an `account_id` and link it afterwards by calling [POST /liveness/enroll](/api-reference/face-match/enroll) with the `session_id` and the `account_id` once you have it.
```bash theme={null}
curl --location 'https://api.prod.verisoul.ai/liveness/enroll' \
--header 'Content-Type: application/json' \
--header 'x-api-key: {VERISOUL_API_KEY}' \
--data '{
"session_id": "{VERISOUL_SESSION_ID}",
"account_id": "{YOUR_ACCOUNT_ID}"
}'
```
Enrolling has the same effect as passing `account_id` up front: the account is created and the session joins the deduplication pool. The session must be complete and enrolled **within 30 days** of creation, and it can only be tied to one account — enrolling a session that already has an account returns `session_already_tied_to_an_account`.
Whenever you do know the identifier up front, prefer passing `account_id` on the session request — it is one fewer call and the account context is available the moment the session completes.
## Next Steps
Once you have obtained a session token, you can proceed to navigate the user to Face Match to complete the verification process.
# Overview
Source: https://docs.verisoul.ai/verifications/face-match/integration/integration
How to integrate Face Match into your application
## Integration Flow
Your backend server requests a session token from Verisoul's API
Your application directs the user to Face Match with the session token
After the user completes verification, you can verify uniqueness or authenticate the user
## Example Implementation
For a working example of the integration, see our sample app. You can customize decisioning logic and try the verification workflow yourself.
Complete reference implementation demonstrating Face Match integration in a React Native application
# Localization
Source: https://docs.verisoul.ai/verifications/face-match/integration/localization
Configure Face Match to support different languages
## Configuration
You can choose to run Face Match in one of sixteen supported languages by passing the `lng` query parameter with the desired language codewhen navigating to the FaceMatch URL.
## Supported Languages
If you need support for a language not listed here, please [contact us](mailto:support@verisoul.ai) to discuss your requirements.
lng=en
lng=es
lng=zh
lng=ar
lng=fr
lng=de
lng=ja
lng=ko
lng=pt
lng=af
lng=bn
lng=el
lng=kk
lng=no
lng=ru
lng=tr
lng=vi
## Example
If you wanted to serve Face Match in Spanish, you would navigate to:
```
https://app.sandbox.verisoul.ai?session_id={VERISOUL_SESSION_ID}&lng=es
```
# Navigate to Face Match
Source: https://docs.verisoul.ai/verifications/face-match/integration/navigate-to-facematch
How to direct users to the Face Match verification interface
Once you have fetched a valid session token, you can navigate the user to Verisoul's Face Match URL to complete the verification process.
## Face Match URL
The Face Match URL is:
`https://app.{env}.verisoul.ai/` where `{env}` is:
* `prod` for production
* `sandbox` for sandbox
The Face Match URL accepts the following query parameters:
The session token obtained from the Verisoul API
The URL encoded string to redirect to after the verification is complete (default: [https://verisoul.ai](https://verisoul.ai))
Optional language parameter to customize the instructions (default: en)
See [Localization](/verifications/face-match/integration/localization) for more details
## On Completion
Once the Face Match session is complete, the user will be sent to the `redirect_url` configured in the query parameters. By default, Face Match redirects to `https://verisoul.ai`.
The completed redirect URL will contain the following parameters:
The session ID used for the verification
Whether the verification was successful
Present only if success is `false`, contains the reason for failure
## Navigation Options
For web applications, you can redirect the user to the Face Match URL:
```javascript theme={null}
function redirectToFaceMatch(sessionId) {
const redirectUrl = encodeURIComponent(window.location.origin + '/verification-complete');
const facematchUrl = `https://app.prod.verisoul.ai/?session_id=${sessionId}&redirect_url=${redirectUrl}`;
window.location.href = facematchUrl;
}
```
If your user is on desktop, Face Match will automatically recognize this and display a QR code so that the user can complete the verification on their mobile device.
For mobile applications, you can open Face Match in a webview:
```javascript theme={null}
import { WebView } from 'react-native-webview';
function FaceMatchVerification({ sessionId, onComplete }) {
const facematchUrl = `https://app.prod.verisoul.ai/?session_id=${sessionId}&redirect_url=yourapp://verification-complete`;
return (
{
// Handle redirect back to your app
if (navState.url.startsWith('yourapp://')) {
// Extract parameters from URL
const url = new URL(navState.url);
const status = url.searchParams.get('status');
const errorMessage = url.searchParams.get('error_message');
onComplete({ status, errorMessage });
}
}}
/>
);
}
```
## Error Handling
Error messages can include:
* **invalid\_session\_id**: The session token provided is expired or invalid
* **session\_id\_not\_found**: No session token was provided
* **liveness\_check\_failed**: The session ended in a non-success state, for example the user cancelled a step
* **face\_match\_failed**: The face match/liveness step reached the maximum number of attempts without succeeding
* **failed\_to\_collect\_device\_data**: Required device or network data could not be collected from the user's device
* **max\_duration\_exceeded**: The session was never completed and can no longer be completed. Sessions stay completable for 30 days after creation, so this can arrive well after the user last interacted with the flow
It is recommended to parse the error\_message and prompt the user to restart the Face Match process. To let a user retry, you will need to fetch a new session token.
Webhooks give your backend a record of every session outcome, even when the user's browser never reaches this redirect. See [Webhooks](/verifications/webhooks) for more information.
## Next Steps
After the user completes the Face Match verification, you can verify the user to implement uniqueness checks or authentication.
# Response Signals
Source: https://docs.verisoul.ai/verifications/face-match/integration/response-signals
Interpret the signals returned by Face Match verification
When you call [/verify-face](/api-reference/face-match/verify-face), Verisoul returns an overall decision along with the underlying risk signals captured during the session. Use this page as a reference for interpreting everything the endpoint returns.
## Interpreting the Response
The endpoint provides three values to help interpret the risk of the user:
1. **Decision** -> an overall decision; Real, Suspicious, or Fake
2. **Risk Score** -> a 0-1 number score representing the overall risk of the user
3. **Risk Flags** -> an array of string flags that summarize the risky aspects of the user's session
See here for the full set of [risk flags](/verifications/face-match/resources/risk-flags)
For more nuanced decisioning you can also interpret the other risk signals captured in the session.
## Device and Network Signals
Device and network Signals are information about the user's exact device and connecting network.
| Name |
Description |
| ip\_document\_country\_mismatch |
Current IP geolocation country does not match document geolocation |
| device\_risk |
Device likely emulator, VM |
| proxy |
Face Match on a proxy IP |
| vpn |
Face Match on a VPN IP |
| datacenter |
Face Match on a datacenter |
| tor |
Face Match on a TOR IP |
| recent\_fraud\_ip |
Face Match on an IP reported as fraud |
| device\_network\_mismatch |
Device user agent is inconsistent with connecting network fingerprint |
## Referring Session Signals
Referring Session Signals will only be present if you initialized the session with a `referring_session_id` parameter.
| Name |
Description |
| impossible\_travel |
Referring session geolocation is far from Face Match geolocation relative to timespan |
| ip\_mismatch |
Referring session IP different than Face Match session IP |
| user\_agent\_mismatch |
Referring session user agent different than Face Match session user agent |
## Multi Accounting
Face Match will calculate any potential matches based on three identifiers captured during all sessions.
| Match Type |
Description |
| browser |
Face Match session was completed on the same browser session |
| ip\_address |
Face Match session shares the same ip address |
| face |
Face Match session shares the same face |
# Verify User
Source: https://docs.verisoul.ai/verifications/face-match/integration/verify-and-enroll
Get the verification result for a Face Match session
Once a Face Match session is complete, call [/verify-face](/api-reference/face-match/verify-face) to get the user's **Decision**, **Risk Score**, and **Risk Flags**. See [Response Signals](/verifications/face-match/integration/response-signals) for how to interpret everything the endpoint returns.
If you passed an `account_id` [when creating the session](/verifications/face-match/integration/initialize-session#account-id), the response also includes account context:
* **`lists`** — names of the lists the account is currently a member of. If the account is on an allow list, `decision` is `Real`; on a block list, `Fake`.
* **`rules`** — names of [rules](/action/rules) that have triggered for this account.
Both fields are empty arrays (`[]`) for sessions not linked to an account. See [Rules, Lists, and Verifications](/action/rules#rules-lists-and-verifications) for details.
To link an `account_id` to a session that has already completed, use the [Enroll endpoint](/api-reference/face-match/enroll).
For strict 1:1 sign-in authentication (matching a returning user against their own enrolled face), see [Authentication](/verifications/face-match/integration/authentication), which uses the [/verify-identity](/api-reference/face-match/verify-identity) endpoint.
## API Reference
For complete details on all available endpoints and parameters, see the [Face Match API Reference](/api-reference/face-match/session).
# Overview
Source: https://docs.verisoul.ai/verifications/face-match/overview
Understanding and implementing Verisoul Face Match
## What is Face Match?
Face Match is a biometric verification method that uses facial recognition technology to verify user identities. The workflow uses facial biometrics to match and deduplicate users, while ensuring that they're humans through advanced liveness detection.
Ensure the verification is being performed by a real person
Deduplicate users to verify a user is unique across your platform
Validate the device and network to ensure the verification is secure
Match a user with an enrolled account to ensure it's the same person
## How It Works
The user takes a selfie or short video using their device camera
Our system verifies that it's a real person, not a photo or deepfake. Then it extracts a biometric template from the user's face.
Get the results from the user's Face Match session:
- Liveness Detection: Verify that the user is a real person
- Verify Uniqueness: Verify that the user's is not a repeat user
- Device and Network Validation: Validate the device and network are non risky to prevent impersonation and fraud farms
- Authentication: Validate the face against a previously enrolled account
## When to Use Face Match
For high-value services requiring identity verification
Requiring additional verification for security
Of high-risk accounts to ensure continued legitimacy
In gaming, gambling, or promotional offers
Face Match is designed to enhance security while maintaining user privacy. All biometric data is processed with the highest security standards and in compliance with relevant privacy regulations. Learn more about our [biometric compliance](/verifications/face-match/resources/biometric-compliance).
## See It In Action
## Get Started
Ready to implement Face Match in your application? Check out [Quickstart](/verifications/face-match/quickstart) to get up and running quickly.
# Quickstart
Source: https://docs.verisoul.ai/verifications/face-match/quickstart
Get started with Verisoul Face Match verification
This guide will help you quickly implement Face Match verification in your application. The entire process takes just a few minutes to set up.
To run the app a Verisoul API Key is required. Schedule a call to get started.
## Get Started
Use your Verisoul API Key to create a new Face Match session:
```bash theme={null}
curl --location 'https://api.sandbox.verisoul.ai/liveness/session?account_id=facematch-quickstart-account' \
--header 'x-api-key: {VERISOUL_API_KEY}'
```
This will return a `session_id` that you'll need for the next steps.
`account_id` is your unique identifier for the user — replace `facematch-quickstart-account` with your actual user's identifier. The completed session is tied to this account automatically.
Direct your user to the Face Match verification page in any browser:
```
https://app.sandbox.verisoul.ai?session_id={VERISOUL_SESSION_ID}
```
The user will complete a quick verification process that includes taking a selfie video and completing liveness checks.
Get the verification result for the completed session:
```bash theme={null}
curl --location 'https://api.sandbox.verisoul.ai/liveness/verify-face' \
--header 'Content-Type: application/json' \
--header 'x-api-key: {VERISOUL_API_KEY}' \
--data '{
"session_id": "{VERISOUL_SESSION_ID}"
}'
```
The response includes the **Decision**, **Risk Score**, and **Risk Flags**. See [Response Signals](/verifications/face-match/integration/response-signals) for how to interpret them.
## That's it!
Now you can navigate to the [Face Match dashboard](https://dashboard.verisoul.ai) to see a record of your session.
### Alternative Testing Option
If you prefer to quickly test Face Match with some users, we offer a Public Redirect endpoint that doesn't require a full integration.
This endpoint provides a *public* URL that you can share with test users. Please note that this is not the recommended integration method and is intended only for temporary testing purposes. Since the redirect link is completely public, users can share it with others, which may lead to increased costs.
For more details on this endpoint, see our [API Reference](/api-reference/face-match/public-redirect).
## Next Steps
Detailed instructions for integrating Face Match into your application
Complete API documentation for Face Match endpoints
# Best Practices and Tips
Source: https://docs.verisoul.ai/verifications/face-match/resources/best-practices
How to maximize conversion rates and minimize support for Face Match
## Integration Tips
Follow these tips to maximize user conversion rates:
* **Mobile Optimization**: Have users complete Face Match on mobile. The Face Match UI is optimized for mobile phones (and works on every phone). If a user is on desktop, we recommend showing them a QR code to open and complete on mobile. Reach out if you need help with this.
* **Share Support Resources**: Share Verisoul's tips & tricks hub with users. If a user needs support, share Verisoul's pre-built tips section to help them complete the verification quickly! [Tips Link](https://hub.verisoul.ai/tips)
* **Clear Communication**: Tell the user what is happening. We recommend letting your users know why they will be verified with FaceMatch and the benefits to them. For example:
* Let's make sure you are real and help us distinguish between real users and fake ones. This check keeps \[your application] safe and only takes 30 seconds to complete (no app download required)
* **Webview Integration**: Run Face Match in a webview when integrating into a mobile app. Instead of having the user leave app and use native browser it is recommended to run Face Match in a webview within the app. For an example of this type of integration see the [react-native sample app](https://github.com/verisoul/react-native-facematch-sample-app).
## User Tips
### How to Succeed
Users should follow these steps to maximize success rates:
* **Find Good Lighting**: The user should find a light source and try to maximize light on their face, while avoiding light sources behind them (which create shadow).
* **Follow On-screen Instructions**: The UI will tell users when to move closer or further.
* **Turn Up Device Brightness**: The UI will turn white on the liveness check screen, so a bright screen will help illuminate their face.
### Common Failures
* **Camera Issues**:
* User camera feed is being used by another app (we will detect a potential security risk)
* User doesn't allow camera permissions
* User camera is blurry or lighting is bad
* **Device Orientation**: User device is in landscape mode (needs to be in portrait - if a mobile device)
* **Session Expiration**: User session is expired - they received a session from your app, but waited longer than 30 days to start / complete the session.
# Biometric Compliance
Source: https://docs.verisoul.ai/verifications/face-match/resources/biometric-compliance
Understanding Verisoul's biometric data handling and compliance
## Biometric Data Handling
Verisoul handles all aspects of biometric data capture, including user permissioning, data collection, processing, storage, and retention. We interact directly with the end user and obtain their affirmative, informed consent, ensuring full compliance with applicable biometric data laws.
As a result, Verisoul customers do not directly collect, process, store, or manage any biometric or document-related data, and are therefore not required to implement additional biometric compliance measures.
* For details on our biometric data handling, including storage, security, retention, and our compliance with regulations such as BIPA, please refer to our [Biometric Terms & Conditions Consent Form](https://policies.verisoul.ai/biometric.html).
* For a comprehensive overview of Verisoul's security posture, certifications, and compliance practices, visit our [Trust Center](https://trust.verisoul.ai/).
# Glossary
Source: https://docs.verisoul.ai/verifications/face-match/resources/glossary
Key terms and definitions for Face Match verification
## 1:N Identification
Authentication whereby an identity is captured and compared with all previously captured identities. The search results are used to establish the identity of a person. In short, a 1:N Identification answers the question:
**Who is this person and are they unique?**
**Examples of a 1:N Identification include:**
* Any place that requires Social Security Number (SSN)
* ID/Passport issuance
* Verisoul's Liveness & Uniqueness solution
## 1:1 Verification
Authentication whereby an identity is captured and compared with a single previously captured identity. In short, a 1:1 Verification answers the question:
**Is this person who they claim to be?**
**Examples of a 1:1 Verification include:**
* Apple's FaceID
* CLEAR travel biometrics
* Inputting a PIN at a bank ATM
This approach is usually made more secure by adding an additional "factor" of authentication like having possession of a phone, password, etc.
# Risk Flags
Source: https://docs.verisoul.ai/verifications/face-match/resources/risk-flags
A comprehensive list of risk flags returned by Face Match
| Risk Flag |
Category |
Risk Level |
Explanation |
| high\_device\_risk |
Device |
High |
Device likely emulator, VM |
| proxy\_detected |
Network |
Moderate |
Session on a proxy IP |
| vpn\_detected |
Network |
Moderate |
Session on a VPN |
| datacenter\_detected |
Network |
Moderate |
Session on a datacenter IP |
| recent\_fraud\_ip |
Network |
Low |
IP recently reported as fraud |
| impossible\_travel\_detected |
Impersonation |
High |
Referring session geolocation is far from session geolocation relative to timespan |
| repeat\_face |
Multi-Accounting |
High |
Face has been seen in your application under a different account |
| repeat\_device |
Multi-Accounting |
High |
Device has been used for multiple sessions |
| different\_device\_type\_same\_category |
Impersonation |
Moderate |
Referring session and verification session are same device category (e.g., mobile), but are different types (iPhone, Android) |
| same\_device\_type\_different\_ip |
Impersonation |
Moderate |
Referring session and verification session are same device type (e.g., iPhone), but are different IPs |
| potential\_link\_sharing |
Impersonation |
Low |
Referring session is likely sharing the verification link |
| location\_spoofing |
Impersonation |
High |
The user is actively trying to obfuscate their current location |
| referring\_ip\_mismatch |
Impersonation |
Low |
Referring session IP different than verification session IP |
| referring\_user\_agent\_mismatch |
Impersonation |
Low |
Referring session user agent different than verification user agent |
| referring\_device\_timezone\_mismatch |
Impersonation |
Moderate |
Referring session device timezone is a meaningfully different region than verification timezone |
| referring\_ip\_timezone\_mismatch |
Impersonation |
Moderate |
Referring session ip timezone is a meaningfully different region than verification timezone |
# Security and Accuracy
Source: https://docs.verisoul.ai/verifications/face-match/resources/security-accuracy
Understanding the security features and accuracy metrics of Face Match
## Security
| Risk |
Security Functionality |
| 2D Images, Print Outs |
3D FaceScan detects points in three-dimensions, meaning 2D images and prints are quickly dismissed |
| 3D Masks, Ultra-realistic wax sculptures |
A) 3D Liveness detection checks for liveness context throughout user's face over time, meaning static or partially static faces (partially altered with wax/mask) will be detected
B) Active movement ensures user liveness; additionally, multiple user distances from camera generates high-fidelity liveness data
|
| Video injection & DeepFakes |
Technology detects when camera feed is being altered or user is trying to inject video
Additionally, technology detects deepfake videos & images
|
| FaceScan alteration |
FaceScans are encrypted to prevent alteration on the client side |
| Client-side device risk |
A) SDK checking for risk signals on the device that would indicate likely fraud
B) Use obfuscation and checksums to ensure code base is not tampered with
|
## Accuracy
### Definitions
Imagine User A is already enrolled...
**False Acceptance Rate (FAR)** - this is the probability that a given user B can pretend to be User A
* This is the value that matters for authentication (getting into an existing account)
**False Rejection Rate (FRR)** - this is the probability that User A will be rejected when trying to authenticate again
* This is the value that matters for uniqueness (preventing an existing user from falsely creating a duplicate / new account)
### Accuracy:
* **FAR: 1 / 125,000,000 chance** (Apple's touch ID is 1/50K, and FaceID is 1/1M)
* **FRR: \< 3 / 100,000**
* **Works with beards, transparent glasses (not sunglasses), and makeup**
* **3-Dimensional** modeling based on facial features results in skin-tone agnostic accuracy
# Simulate Face Match
Source: https://docs.verisoul.ai/verifications/face-match/resources/simulate-face-match
Bypass verification when testing
# Simulation Mode
When developing and testing your Face Match integration, you can use simulation mode to test different scenarios without performing actual liveness checks. This makes development and testing much faster and more efficient.
Simulation mode is only available in the sandbox environment
## How to Use Simulation Mode
To use simulation mode, simply add the `simulate` query parameter when getting a Face Match session\_id.
When you navigate to the user with that session, the page will wait for 3 seconds before redirecting with the simulated response, mimicking the real-world flow.
The session will redirect with the same error or success case that you specified in the `simulate` parameter.
### Available Simulation Cases
The following simulation cases are available for testing:
* `invalid_session_id` - Simulates an invalid session ID error
* `session_id_not_found` - Simulates a case where the session ID cannot be found
* `failed_to_get_camera_permission` - Simulates a camera permission failure
* `failed_to_complete_face_scan` - Simulates a face scan failure
* `failed_to_collect_device_data` - Simulates a device data collection failure
* `liveness_check_failed` - Simulates a failed liveness check
* `success` - Simulates a successful verification
### Example Testing Flow
1. Add the `simulate` parameter to [GET /liveness/session](/api-reference/face-match/session#parameter-simulate) with the case you want to test
```
GET /liveness/session?simulate=liveness_check_failed
{
"request_id": "c6716efe-3624-4813-9a59-175d8fee484c",
"session_id": "{some-simulated-session-id}"
}
```
2. Navigate to the Face Match URL like normal and wait 3 seconds
```
https://app.sandbox.verisoul.ai?session_id={some-simulated-session-id}
```
3. The page will redirect you with the correct error or success message, bypassing any liveness or document checks
```
https://verisoul.ai?session_id={some-simulated-session-id}&success=false&error_message=liveness_check_failed
```
## Choose the Decision and Score
With `simulate=success`, you can set the outcome that comes back by adding these optional parameters to [GET /liveness/session](/api-reference/face-match/session):
| Parameter | Values | Description |
| - | - | - |
| `simulate_decision` | `Real`, `Suspicious`, `Fake` | The `decision` returned by `/verify-face` and the webhook. |
| `simulate_risk_score` | `0` to `1` | The `risk_score` returned by `/verify-face` and the webhook. |
| `simulate_risk_flags` | Comma-separated flag names, up to 20 | The `risk_flags` returned by `/verify-face` and the webhook. Use lowercase names such as `vpn_detected,repeat_face`. |
When you pass only `simulate_decision`, `risk_score` is filled with a typical value: `0.1` for `Real`, `0.5` for `Suspicious` and `0.9` for `Fake`. Values are returned as you send them. Verisoul does not compare the decision to the score or to your project thresholds.
If you pass `simulate_decision` or `simulate_risk_score` without `simulate_risk_flags`, `risk_flags` is an empty list. If you pass none of the three, you receive the default stubbed response (decision `Fake`).
These parameters require `simulate=success`. A request that sends them with any other `simulate` value, or an invalid value, returns a 400 with `invalid_simulated_session`.
### Examples
```
# Approval path
GET /liveness/session?simulate=success&simulate_decision=Real
# Manual review path with a specific score
GET /liveness/session?simulate=success&simulate_decision=Suspicious&simulate_risk_score=0.55
# Rejection path with specific flags
GET /liveness/session?simulate=success&simulate_decision=Fake&simulate_risk_score=0.95&simulate_risk_flags=repeat_face,vpn_detected
```
## Session Data in Responses
Responses for simulated sessions carry the data of the session you created. The `metadata` object in `/verify-face` and in the webhook contains the real `project_id`, `session_id`, `request_id` and `timestamp`. `account_id` is the `account_id` you passed when creating the session, or `null`. `referring_session_id` is the `referring_session_id` you passed, or `null`. You can match a simulated webhook to your own records the same way you match a real one.
## Webhooks
Simulated sessions send the same webhooks as real sessions, to the endpoints subscribed in your project:
| `simulate` value | Webhook |
| - | - |
| `success` | `verification.face-match.completed` with `status: "success"`. The `data` object matches the `/verify-face` response for the session. |
| `liveness_check_failed`, `face_match_failed`, `failed_to_collect_device_data` | `verification.face-match.completed` with `status: "error"` and the matching error code. |
| `invalid_session_id`, `session_id_not_found` | No webhook. These cases only redirect the user. |
The webhook is sent when the simulated session finishes, about 3 seconds after the user opens the session URL.
## API Responses
When calling the API with sessions that have been simulated you can expect:
* For `success` case: You receive a stubbed response that uses the decision, score and flags you set, or the default stub values when you set none. `metadata` comes from the real session.
* For all other cases: You receive a 400 response
The error responses in simulation mode may not exactly match the production error responses. They are intended for testing the basic flow of your integration.
This allows you to test your integration's error handling and success flows without needing to perform actual verifications.
# Initialize Session
Source: https://docs.verisoul.ai/verifications/id-check/integration/initialize-session
How to fetch a session token for ID Check verification
For a user to go through ID Check, you must first fetch a valid session token. This token establishes the authenticity throughout the verification process.
Session tokens are valid for **30 days** from creation. You do not have to generate one immediately before the user starts — you can create it ahead of time and deliver it asynchronously, such as in an emailed or texted verification link. The user must start and complete ID Check before the token expires.
ID Check sessions cannot be initialized for projects that have groups enabled. If your project uses groups, you will need to use a different project for ID Check verification.
## Get Session ID
Your application's server makes an API request to the [GET /liveness/session](/api-reference/id-check/session) endpoint with two query parameters: `id=true`, which makes it an ID Check session, and `account_id`, which links the session to the user you are verifying.
```
GET /liveness/session?id=true&account_id=
```
```javascript Node theme={null}
try {
// Create a session token via Verisoul's API
let response = await fetch(
`https://api.prod.verisoul.ai/liveness/session?id=true&account_id=${accountId}`,
{
method: 'GET',
headers: {
'x-api-key': '{VERISOUL_API_KEY}'
}
}
);
let {session_id} = await response.json();
// Return the session_id to your client application
// ...
} catch (err) {
console.error(err);
}
```
```bash cURL theme={null}
curl --location 'https://api.prod.verisoul.ai/liveness/session?id=true&account_id={YOUR_ACCOUNT_ID}' \
--header 'x-api-key: {VERISOUL_API_KEY}'
```
Use `https://api.prod.verisoul.ai` for production and `https://api.sandbox.verisoul.ai` for testing. See [Environments](/api-reference/introduction#environments).
For security, best practice is to fetch the session token from a protected server, never on the client.
## Parameters
| Parameter | Required | Description |
| - | - | - |
| `id` | **Always** | Must be `id=true`. This is what makes the session an ID Check. |
| `account_id` | **Strongly recommended** | Your stable identifier for the user. Links the completed session to that account. |
| `referring_session_id` | Optional | The user's current Verisoul platform session, for richer risk assessment. |
| `simulate` | Optional | Forces a specific outcome in sandbox. See [Simulate ID Check](/verifications/id-check/resources/simulate-id-check). |
| `simulate_decision`, `simulate_risk_score`, `simulate_risk_flags` | Optional | Sandbox only. With `simulate=success`, sets the decision, score and flags returned by the simulated session. See the simulate page linked above. |
### The id Parameter
ID Check and [Face Match](/verifications/face-match/integration/initialize-session) share the same `/liveness/session` endpoint, and `id=true` is the only thing that distinguishes them.
* **With `id=true`** — the user takes a selfie video **and** uploads an identity document. Verify the session with [/verify-id](/api-reference/id-check/verify-id).
* **Without `id=true`** — you get a Face Match session. The user only takes a selfie video and is never asked for a document.
If you omit `id=true`, the session silently becomes a Face Match session. The user will complete it without ever being prompted for their ID, and `/verify-id` will not return document results.
### Account ID
`account_id` is your own stable identifier for the user — the same one you use elsewhere in your Verisoul integration. Pass it on every session you can.
Linking a session to an account is what turns a one-off verification into a persistent identity. When the session completes it is tied to the account automatically, which:
* **Adds the session to the 1:N deduplication pool.** The face and the identity document become searchable, so every later ID Check is compared against them. A future session matching this face or document under a *different* `account_id` is flagged with [`repeat_face` or `repeat_id`](/verifications/id-check/resources/risk-flags).
* **Creates an account on the real-time platform.** This is the same account you would create by calling [/session/authenticate](/api-reference/session/authenticate). It appears in the dashboard and accumulates [account-level signals](/signals-scores/account-vs-session) such as `multiple_accounts` and `accounts_linked`.
* **Returns account context on the verify response.** List memberships and triggered [rules](/action/rules) come back from [/verify-id](/verifications/id-check/integration/verify-and-enroll), and the decision respects your allow and block lists.
A session that never receives an `account_id` — not on the session request and not through [enrollment](#enrolling-an-account-id-later) — is never added to the deduplication pool. You still get a decision for that one verification, but no future session will ever match against its face or document, it does not appear as an account, and `lists` and `rules` come back as empty arrays.
**Repeat verifications of the same user are not multi-accounting.** Deduplication compares *across* `account_id` values, so an account with several ID Check sessions — retries, step-ups, or periodic re-verification — will not raise `repeat_face` or `repeat_id` for matching itself. Those flags only fire when the same face or document shows up under a different `account_id`. Always reuse the same `account_id` for the same user instead of generating a new one per verification.
`account_id` must be a non-empty string; an empty or malformed value returns a `400 invalid_account_id` error.
### Referring Session ID
When using ID Check alongside Verisoul's platform, you can optionally pass a `referring_session_id` parameter in your session request.
This parameter should be the session ID from the user's current interaction with Verisoul's platform, before they begin ID Check. By providing this context, Verisoul can make more informed risk assessments during the verification process.
Passing a referring session ID provides several benefits:
* Enhanced risk assessment capabilities for ID Check
* Access to referring session signals during verification
* Better detection of impersonation attempts and ID selling
For more details on the API parameters, see the [session endpoint](/api-reference/id-check/session#parameter-referring-session-id). To learn about the additional risk signals available when using a referring session ID, refer to the [referring session signals](/verifications/id-check/integration/response-signals#referring-session-signals).
## Enrolling an Account ID Later
Some flows run KYC before the account exists — for example, verifying a user before they finish signing up. In that case, create the session without an `account_id` and link it afterwards by calling [POST /liveness/enroll](/api-reference/id-check/enroll) with the `session_id` and the `account_id` once you have it.
```bash theme={null}
curl --location 'https://api.prod.verisoul.ai/liveness/enroll' \
--header 'Content-Type: application/json' \
--header 'x-api-key: {VERISOUL_API_KEY}' \
--data '{
"session_id": "{VERISOUL_SESSION_ID}",
"account_id": "{YOUR_ACCOUNT_ID}"
}'
```
Enrolling has the same effect as passing `account_id` up front: the account is created and the session joins the deduplication pool. The session must be complete and enrolled **within 30 days** of creation, and it can only be tied to one account — enrolling a session that already has an account returns `session_already_tied_to_an_account`.
Whenever you do know the identifier up front, prefer passing `account_id` on the session request — it is one fewer call and the account context is available the moment the session completes.
## Next Steps
Once you have obtained a session token, you can proceed to navigate the user to ID Check to complete the verification process.
# Overview
Source: https://docs.verisoul.ai/verifications/id-check/integration/integration
How to integrate ID Check into your application
## Integration Flow
Your backend server requests a session token from Verisoul's API
Your application directs the user to ID Check with the session token
After the user completes verification, you can verify the user's identity document, liveness and face-to-id matching
## Example Implementation
For a working example of the integration, see our sample app. You can customize decisioning logic and try the verification workflow yourself.
Complete reference implementation demonstrating ID Check integration in a React Native application
# Localization
Source: https://docs.verisoul.ai/verifications/id-check/integration/localization
Configure ID Check to support different languages
## Configuration
You can choose to run ID Check in one of sixteen supported languages by passing the `lng` query parameter with the desired language code when navigating to the ID Check URL.
## Supported Languages
If you need support for a language not listed here, please [contact us](mailto:support@verisoul.ai) to discuss your requirements.
lng=en
lng=es
lng=zh
lng=ar
lng=fr
lng=de
lng=ja
lng=ko
lng=pt
lng=af
lng=bn
lng=el
lng=kk
lng=no
lng=ru
lng=tr
lng=vi
## Example
If you wanted to serve ID Check in Spanish, you would navigate to:
```
https://app.sandbox.verisoul.ai?session_id={VERISOUL_SESSION_ID}&lng=es
```
# Navigate to ID Check
Source: https://docs.verisoul.ai/verifications/id-check/integration/navigate-to-idcheck
How to direct users to the ID Check verification interface
Once you have fetched a valid session token, you can navigate the user to Verisoul's ID Check URL to complete the verification process.
## ID Check URL
The ID Check URL is:
`https://app.{env}.verisoul.ai/` where `{env}` is:
* `prod` for production
* `sandbox` for sandbox
The ID Check URL accepts the following query parameters:
The session token obtained from the Verisoul API
The URL encoded string to redirect to after the verification is complete (default: [https://verisoul.ai](https://verisoul.ai))
Optional language parameter to customize the instructions (default: en)
See [Localization](/verifications/id-check/integration/localization) for more details
## On Completion
Once the ID Check session is complete, the user will be sent to the `redirect_url` configured in the query parameters. By default, ID Check redirects to `https://verisoul.ai`.
The completed redirect URL will contain the following parameters:
The session ID used for the verification
Whether the verification was successful
Present only if success is `false`, contains the reason for failure
## Navigation Options
For web applications, you can redirect the user to the FaceMatch URL:
```javascript theme={null}
function redirectToIDCheck(sessionId) {
const redirectUrl = encodeURIComponent(window.location.origin + '/verification-complete');
const idcheckUrl = `https://app.prod.verisoul.ai/?session_id=${sessionId}&redirect_url=${redirectUrl}`;
window.location.href = idcheckUrl;
}
```
If your user is on desktop, Face Match will automatically recognize this and display a QR code so that the user can complete the verification on their mobile device.
For mobile applications, you can open Face Match in a webview:
```javascript theme={null}
import { WebView } from 'react-native-webview';
function IDCheckVerification({ sessionId, onComplete }) {
const idcheckUrl = `https://app.prod.verisoul.ai/?session_id=${sessionId}&redirect_url=yourapp://verification-complete`;
return (
{
// Handle redirect back to your app
if (navState.url.startsWith('yourapp://')) {
// Extract parameters from URL
const url = new URL(navState.url);
const status = url.searchParams.get('status');
const errorMessage = url.searchParams.get('error_message');
onComplete({ status, errorMessage });
}
}}
/>
);
}
```
## Error Handling
Error messages can include:
* **invalid\_session\_id**: The session token provided is expired or invalid
* **session\_id\_not\_found**: No session token was provided
* **liveness\_check\_failed**: The session ended in a non-success state, for example the user cancelled a step
* **face\_match\_failed**: The face match/liveness step reached the maximum number of attempts without succeeding
* **failed\_to\_process\_id\_scan**: The identity document scan could not be processed
* **failed\_to\_collect\_device\_data**: Required device or network data could not be collected from the user's device
* **max\_duration\_exceeded**: The session was never completed and can no longer be completed. Sessions stay completable for 30 days after creation, so this can arrive well after the user last interacted with the flow
It is recommended to parse the error\_message and prompt the user to restart the ID Check process. To let a user retry, you will need to fetch a new session token.
Webhooks give your backend a record of every session outcome, even when the user's browser never reaches this redirect. See [Webhooks](/verifications/webhooks) for more information.
## Next Steps
After the user completes the ID Check verification, you can verify the user to implement uniqueness checks or authentication.
# Response Signals
Source: https://docs.verisoul.ai/verifications/id-check/integration/response-signals
Interpret the signals returned by ID Check verification
When you call [/verify-id](/api-reference/id-check/verify-id), Verisoul returns an overall decision along with the underlying risk signals captured during the session. Use this page as a reference for interpreting everything the endpoint returns.
## Interpreting the Response
The endpoint provides three values to help interpret the risk of the user and their document:
1. **Decision** -> an overall decision; Real, Suspicious, or Fake
2. **Risk Score** -> a 0-1 number score representing the overall risk of the user
3. **Risk Flags** -> an array of string flags that summarize the risky aspects of the user's session
See here for the full set of [risk flags](/verifications/id-check/resources/risk-flags)
For more nuanced decisioning you can also interpret the other risk signals captured in the session.
## Device and Network Signals
Device and network Signals are information about the user's exact device and connecting network.
| Name |
Description |
| device\_risk |
Device likely emulator, VM |
| proxy |
ID Check on a proxy IP |
| vpn |
ID Check on a VPN IP |
| datacenter |
ID Check on a datacenter |
| tor |
ID Check on a TOR IP |
| recent\_fraud\_ip |
ID Check on an IP reported as fraud |
| device\_network\_mismatch |
Device user agent is inconsistent with connecting network fingerprint |
## Document Signals
Document Signals refers to the authenticity of the identity document.
| Name |
Description |
| id\_age |
Age of the person on the identity document, calculated from the date of birth on the ID |
| face\_age\_estimate |
Estimated age bracket of the person in the captured selfie, independent of the date of birth on the ID. The highest bracket the person clearly appears to exceed. One of under8, over8, over13, over16, over18, over21, over25, over30, over40, over50, over65, or null when unavailable |
| id\_face\_match\_score |
Match level of face on ID and face scanned. 1.0 is highest match |
| id\_barcode\_status |
Status of the barcode on the identity document |
| id\_face\_status |
Status of the face on the identity document |
| id\_text\_status |
Status of the text information on the identity document |
| is\_id\_digital\_spoof |
Whether the identity document has been digitally spoofed |
| is\_full\_id\_captured |
Whether ID Check was able to capture the full document |
| id\_validity |
Whether identity document presented is authentic or fake |
## Referring Session Signals
Referring Session Signals will only be present if you initialized the session with a `referring_session_id` parameter.
| Name |
Description |
| impossible\_travel |
Referring session geolocation is far from ID Check geolocation relative to timespan |
| ip\_mismatch |
Referring session IP different than ID Check session IP |
| user\_agent\_mismatch |
Referring session user agent different than ID Check session user agent |
## Multi Accounting
ID Check will calculate any potential matches based on five identifiers captured during all sessions.
| Match Type |
Description |
| browser |
ID Check session was completed on the same browser session |
| ip\_address |
ID Check session shares the same ip address |
| face |
ID Check session shares the same face |
| document\_name |
ID Check session shares the same name and date of birth |
| document\_number |
ID Check session shares the same document type and number |
# Verify User
Source: https://docs.verisoul.ai/verifications/id-check/integration/verify-and-enroll
Get the verification result for an ID Check session
Once an ID Check session is complete, call [/verify-id](/api-reference/id-check/verify-id) to get the user's **Decision**, **Risk Score**, and **Risk Flags**. See [Response Signals](/verifications/id-check/integration/response-signals) for how to interpret everything the endpoint returns.
If you passed an `account_id` [when creating the session](/verifications/id-check/integration/initialize-session#account-id), the response also includes account context:
* **`lists`** — names of the lists the account is currently a member of. If the account is on an allow list, `decision` is `Real`; on a block list, `Fake`.
* **`rules`** — names of [rules](/action/rules) that have triggered for this account.
Both fields are empty arrays (`[]`) for sessions not linked to an account. See [Rules, Lists, and Verifications](/action/rules#rules-lists-and-verifications) for details.
## Exclude personal data from the response
Add `?excludePII=true` to the [/verify-id](/api-reference/id-check/verify-id) call to leave personal data out of the response. These fields are not returned:
* **`document_data`**
* **`photo_urls`**
* **`video_urls`**
Everything else, including the decision, risk score, flags, and signals, is the same. Without the parameter, the full response is returned. The parameter applies to that call only, and [webhooks](/verifications/webhooks) still carry the full payload.
```bash theme={null}
curl --location 'https://api.sandbox.verisoul.ai/liveness/verify-id?excludePII=true' \
--header 'Content-Type: application/json' \
--header 'x-api-key: {VERISOUL_API_KEY}' \
--data '{
"session_id": "{VERISOUL_SESSION_ID}"
}'
```
To link an `account_id` to a session that has already completed, use the [Enroll endpoint](/api-reference/id-check/enroll).
## API Reference
For complete details on all available endpoints and parameters, see the [ID Check API Reference](/api-reference/id-check/session).
# Overview
Source: https://docs.verisoul.ai/verifications/id-check/overview
Understanding and implementing Verisoul ID Check
## What is ID Check?
ID Check is a biometric verification method that uses facial recognition and document verification technology to verify user identities. The workflow is used to match and deduplicate users, while ensuring that they're humans through advanced liveness detection.
Verify the user's ID document
Ensure the verification is being performed by a real person
Verify that the user's face matches the photo on their ID
Validate the device and network to ensure the verification is secure
## How It Works
The user takes a selfie or short video using their device camera and uploads their ID document
Our system verifies that it's a real person, not a photo or deepfake and extracts the user's ID document details
Get the results from the user's ID Check session:
- Document Verification: Verify the user has a real ID document
- Liveness Detection: Verify that the user is a real person
- Face-to-ID Matching: Verify that the user's face matches the photo on their ID
- Device and Network Validation: Validate the device and network are non risky to prevent ID selling and fraud farms
## When to Use ID Check
For high-value services requiring identity verification
Requiring additional verification for security
Of high-risk accounts to ensure continued legitimacy
In gaming, gambling, or promotional offers
ID Check is designed to enhance security while maintaining user privacy. All biometric data is processed with the highest security standards and in compliance with relevant privacy regulations. Learn more about our [biometric compliance](/verifications/id-check/resources/biometric-compliance).
## See It In Action
## Get Started
Ready to implement ID Check in your application? Check out [Quickstart](/verifications/id-check/quickstart) to get up and running quickly.
# Quickstart
Source: https://docs.verisoul.ai/verifications/id-check/quickstart
Get started with Verisoul ID Check verification
This guide will help you quickly implement ID Check verification in your application. The entire process takes just a few minutes to set up.
To run the app a Verisoul API Key is required. Schedule a call to get started.
## Get Started
Use your Verisoul API Key to create a new ID Check session:
```bash theme={null}
curl --location 'https://api.sandbox.verisoul.ai/liveness/session?id=true&account_id=idcheck-quickstart-account' \
--header 'x-api-key: {VERISOUL_API_KEY}'
```
This will return a `session_id` that you'll need for the next steps.
`account_id` is your unique identifier for the user — replace `idcheck-quickstart-account` with your actual user's identifier. The completed session is tied to this account automatically.
Direct your user to the ID Check verification page in any browser:
```
https://app.sandbox.verisoul.ai?session_id={VERISOUL_SESSION_ID}
```
The user will complete a quick verification process that includes taking a selfie video and uploading their identity document.
Get the verification result for the completed session:
```bash theme={null}
curl --location 'https://api.sandbox.verisoul.ai/liveness/verify-id' \
--header 'Content-Type: application/json' \
--header 'x-api-key: {VERISOUL_API_KEY}' \
--data '{
"session_id": "{VERISOUL_SESSION_ID}"
}'
```
The response includes the **Decision**, **Risk Score**, and **Risk Flags**. See [Response Signals](/verifications/id-check/integration/response-signals) for how to interpret them.
## That's it!
Now you can navigate to the ID Check dashboard to see a record of your session.
### Alternative Testing Option
If you prefer to quickly test ID Check with some users, we offer a Public Redirect endpoint that doesn't a full integration.
This endpoint provides a *public* URL that you can share with test users. However, please note that this is not the recommended integration method and is intended only for temporary testing purposes. Since the redirect link is completely public, users can share it with others, which may lead to increased costs.
For more details on this endpoint, see our [API Reference](/api-reference/id-check/public-redirect).
## Next Steps
Detailed instructions for integrating ID Check into your application
Complete API documentation for ID Check endpoints
# Best Practices and Tips
Source: https://docs.verisoul.ai/verifications/id-check/resources/best-practices
How to maximize conversion rates and minimize support for ID Check
## Integration Tips
Follow these tips to maximize user conversion rates:
* **Mobile Optimization**: Have users complete ID Check on mobile. The ID Check UI is optimized for mobile phones (and works on every phone). If a user is on desktop, we recommend showing them a QR code to open and complete on mobile. Reach out if you need help with this.
* **Share Support Resources**: Share Verisoul's tips & tricks hub with users. If a user needs support, share Verisoul's pre-built tips section to help them complete the verification quickly! [Tips Link](https://hub.verisoul.ai/tips)
* **Clear Communication**: Tell the user what is happening. We recommend letting your users know why they will be verified with FaceMatch and the benefits to them. For example:
* Let's make sure you are real and help us distinguish between real users and fake ones. This check keeps \[your application] safe and only takes 30 seconds to complete (no app download required)
* **Webview Integration**: Run ID Check in a webview when integrating into a mobile app. Instead of having the user leave app and use native browser it is recommended to run ID Check in a webview within the app. For an example of this type of integration see the [react-native sample app](https://github.com/verisoul/react-native-facematch-sample-app).
## User Tips
### How to Succeed
Users should follow these steps to maximize success rates:
* **Find Good Lighting**: The user should find a light source and try to maximize light on their face, while avoiding light sources behind them (which create shadow).
* **Follow On-screen Instructions**: The UI will tell users when to move closer or further.
* **Turn Up Device Brightness**: The UI will turn white on the liveness check screen, so a bright screen will help illuminate their face.
### Common Failures
* **Camera Issues**:
* User camera feed is being used by another app (we will detect a potential security risk)
* User doesn't allow camera permissions
* User camera is blurry or lighting is bad
* **Device Orientation**: User device is in landscape mode (needs to be in portrait - if a mobile device)
* **Session Expiration**: User session is expired - they received a session from your app, but waited longer than 30 days to start / complete the session.
# Biometric Compliance
Source: https://docs.verisoul.ai/verifications/id-check/resources/biometric-compliance
Understanding Verisoul's biometric data handling and compliance
## Biometric Data Handling
Verisoul handles all aspects of biometric data capture, including user permissioning, data collection, processing, storage, and retention. We interact directly with the end user and obtain their affirmative, informed consent, ensuring full compliance with applicable biometric data laws.
As a result, Verisoul customers do not directly collect, process, store, or manage any biometric or document-related data, and are therefore not required to implement additional biometric compliance measures.
* For details on our biometric data handling, including storage, security, retention, and our compliance with regulations such as BIPA, please refer to our [Biometric Terms & Conditions Consent Form](https://policies.verisoul.ai/biometric.html).
* For a comprehensive overview of Verisoul's security posture, certifications, and compliance practices, visit our [Trust Center](https://trust.verisoul.ai/).
# Risk Flags
Source: https://docs.verisoul.ai/verifications/id-check/resources/risk-flags
A comprehensive list of risk flags returned by ID Check
| Risk Flag |
Category |
Risk Level |
Explanation |
| high\_device\_risk |
Device |
High |
Device likely emulator, VM |
| proxy\_detected |
Network |
Moderate |
ID Check on a proxy IP |
| vpn\_detected |
Network |
Moderate |
ID Check on a VPN |
| datacenter\_detected |
Network |
Moderate |
ID Check on a datacenter IP |
| recent\_fraud\_ip |
Network |
Low |
IP recently reported as fraud |
| impossible\_travel\_detected |
ID Selling |
High |
Referring session geolocation is far from ID Check geolocation relative to timespan |
| ip\_document\_country\_mismatch |
ID Selling |
Moderate |
Current IP geolocation country does not match document geolocation |
| cannot\_confirm\_id\_is\_authentic |
ID Fraud |
Moderate |
ID may be spoofed or digital media (image) |
| likely\_fake\_id |
ID Fraud |
High |
ID very likely fake |
| id\_expired |
ID Fraud |
Low |
ID Expiration date is past |
| id\_age\_below\_16 |
COPPA |
High |
DOB on ID indicates user is below 16 years old |
| low\_id\_face\_match\_score |
ID Fraud |
High |
Face does not match ID Photo |
| moderate\_id\_face\_match\_score |
ID Fraud |
Moderate |
Face may not match ID photo |
| id\_is\_damaged |
ID Fraud |
Low |
ID shows structural damage (cracked, bent, chipped, or missing a corner) |
| digital\_id |
ID Fraud |
Low |
Digital ID (phone or screen) detected during the video liveness check |
| repeat\_face |
Multi-Accounting |
High |
Face has been seen in your application under a different account |
| repeat\_id |
Multi-Accounting |
High |
ID has been seen in your application under a different account |
| repeat\_device |
Multi-Accounting |
High |
Device has been used for multiple ID Checks |
| known\_fraud\_face |
Fraud Farm |
Moderate |
Face is associated with fraud |
| known\_fraud\_id |
Fraud Farm |
Moderate |
ID is associated with fraud |
| different\_device\_type\_same\_category |
ID Selling |
Moderate |
Referring session and ID Check session are same device category (e.g., mobile), but are different types (iPhone, Android) |
| same\_device\_type\_different\_ip |
ID Selling |
Moderate |
Referring session and ID Check session are same device type (e.g., iPhone), but are different IPs |
| potential\_link\_sharing |
ID Selling |
Low |
Referring session is likely sharing the verification link |
| location\_spoofing |
ID Selling |
High |
The user is actively trying to obfuscate their current location |
| referring\_ip\_mismatch |
ID Selling |
Low |
Referring session IP different than ID Check session IP |
| referring\_user\_agent\_mismatch |
ID Selling |
Low |
Referring session user agent different than ID Check user agent |
| referring\_device\_timezone\_mismatch |
ID Selling |
Moderate |
Referring session device timezone is a meaningfully different region than verification timezone |
| referring\_ip\_timezone\_mismatch |
ID Selling |
Moderate |
Referring session ip timezone is a meaningfully different region than verification timezone |
# Security and Accuracy
Source: https://docs.verisoul.ai/verifications/id-check/resources/security-accuracy
Understanding the security features and accuracy metrics of ID Check
## Security
| Risk |
Security Functionality |
| 2D Images, Print Outs |
3D FaceScan detects points in three-dimensions, meaning 2D images and prints are quickly dismissed |
| 3D Masks, Ultra-realistic wax sculptures |
A) 3D Liveness detection checks for liveness context throughout user's face over time, meaning static or partially static faces (partially altered with wax/mask) will be detected
B) Active movement ensures user liveness; additionally, multiple user distances from camera generates high-fidelity liveness data
|
| Video injection & DeepFakes |
Technology detects when camera feed is being altered or user is trying to inject video
Additionally, technology detects deepfake videos & images
|
| FaceScan alteration |
FaceScans are encrypted to prevent alteration on the client side |
| Client-side device risk |
A) SDK checking for risk signals on the device that would indicate likely fraud
B) Use obfuscation and checksums to ensure code base is not tampered with
|
## Accuracy
### Definitions
Imagine User A is already enrolled...
**False Acceptance Rate (FAR)** - this is the probability that a given user B can pretend to be User A
* This is the value that matters for authentication (getting into an existing account)
**False Rejection Rate (FRR)** - this is the probability that User A will be rejected when trying to authenticate again
* This is the value that matters for uniqueness (preventing an existing user from falsely creating a duplicate / new account)
### Accuracy:
* **FAR: 1 / 125,000,000 chance** (Apple's touch ID is 1/50K, and FaceID is 1/1M)
* **FRR: \< 3 / 100,000**
* **Works with beards, transparent glasses (not sunglasses), and makeup**
* **3-Dimensional** modeling based on facial features results in skin-tone agnostic accuracy
# Simulate ID Check
Source: https://docs.verisoul.ai/verifications/id-check/resources/simulate-id-check
Bypass verification when testing
# Simulation Mode
When developing and testing your ID Check integration, you can use simulation mode to test different scenarios without performing actual liveness checks or ID scans. This makes development and testing much faster and more efficient.
Simulation mode is only available in the sandbox environment
## How to Use Simulation Mode
To use simulation mode, simply add the `simulate` query parameter when getting an ID Check session\_id.
When you navigate to the user with that session, the page will wait for 3 seconds before redirecting with the simulated response, mimicking the real-world flow.
The session will redirect with the same error or success case that you specified in the `simulate` parameter.
### Available Simulation Cases
The following simulation cases are available for testing:
* `invalid_session_id` - Simulates an invalid session ID error
* `session_id_not_found` - Simulates a case where the session ID cannot be found
* `failed_to_get_camera_permission` - Simulates a camera permission failure
* `failed_to_complete_face_scan` - Simulates a face scan failure
* `failed_to_process_id_scan` - Simulates an ID scan processing failure
* `failed_to_collect_device_data` - Simulates a device data collection failure
* `liveness_check_failed` - Simulates a failed liveness check
* `success` - Simulates a successful verification
### Example Testing Flow
1. Add the `simulate` parameter to [GET /liveness/session](/api-reference/id-check/session#parameter-simulate) URL with the case you want to test
```
GET /liveness/session?simulate=liveness_check_failed
{
"request_id": "c6716efe-3624-4813-9a59-175d8fee484c",
"session_id": "{some-simulated-session-id}"
}
```
2. Navigate to the ID Check URL like normal and wait 3 seconds
```
https://app.sandbox.verisoul.ai?session_id={some-simulated-session-id}
```
3. The page will redirect you with the correct error or success message, bypassing any liveness or document checks
```
https://verisoul.ai?session_id={some-simulated-session-id}&success=false&error_message=liveness_check_failed
```
## Choose the Decision and Score
With `simulate=success`, you can set the outcome that comes back by adding these optional parameters to [GET /liveness/session](/api-reference/id-check/session):
| Parameter | Values | Description |
| - | - | - |
| `simulate_decision` | `Real`, `Suspicious`, `Fake` | The `decision` returned by `/verify-id` and the webhook. |
| `simulate_risk_score` | `0` to `1` | The `risk_score` returned by `/verify-id` and the webhook. |
| `simulate_risk_flags` | Comma-separated flag names, up to 20 | The `risk_flags` returned by `/verify-id` and the webhook. Use lowercase names such as `vpn_detected,repeat_face`. |
When you pass only `simulate_decision`, `risk_score` is filled with a typical value: `0.1` for `Real`, `0.5` for `Suspicious` and `0.9` for `Fake`. Values are returned as you send them. Verisoul does not compare the decision to the score or to your project thresholds.
If you pass `simulate_decision` or `simulate_risk_score` without `simulate_risk_flags`, `risk_flags` is an empty list. If you pass none of the three, you receive the default stubbed response (decision `Fake`).
These parameters require `simulate=success`. A request that sends them with any other `simulate` value, or an invalid value, returns a 400 with `invalid_simulated_session`.
### Examples
```
# Approval path
GET /liveness/session?id=true&simulate=success&simulate_decision=Real
# Manual review path with a specific score
GET /liveness/session?id=true&simulate=success&simulate_decision=Suspicious&simulate_risk_score=0.55
# Rejection path with specific flags
GET /liveness/session?id=true&simulate=success&simulate_decision=Fake&simulate_risk_score=0.95&simulate_risk_flags=repeat_face,vpn_detected
```
## Session Data in Responses
Responses for simulated sessions carry the data of the session you created. The `metadata` object in `/verify-id` and in the webhook contains the real `project_id`, `session_id`, `request_id` and `timestamp`. `account_id` is the `account_id` you passed when creating the session, or `null`. `referring_session_id` is the `referring_session_id` you passed, or `null`. You can match a simulated webhook to your own records the same way you match a real one.
## Webhooks
Simulated sessions send the same webhooks as real sessions, to the endpoints subscribed in your project:
| `simulate` value | Webhook |
| - | - |
| `success` | `verification.id-check.completed` with `status: "success"`. The `data` object matches the `/verify-id` response for the session. |
| `liveness_check_failed`, `face_match_failed`, `failed_to_collect_device_data` | `verification.id-check.completed` with `status: "error"` and the matching error code. |
| `invalid_session_id`, `session_id_not_found`, `failed_to_process_id_scan` | No webhook. These cases only redirect the user. |
The webhook is sent when the simulated session finishes, about 3 seconds after the user opens the session URL.
## API Responses
When calling the API with sessions that have been simulated you can expect:
* For `success` case: You receive a stubbed response that uses the decision, score and flags you set, or the default stub values when you set none. `metadata` comes from the real session.
* For all other cases: You receive a 400 response
The error responses in simulation mode may not exactly match the production error responses. They are intended for testing the basic flow of your integration.
This allows you to test your integration's error handling and success flows without needing to perform actual verifications.
# Supported Documents
Source: https://docs.verisoul.ai/verifications/id-check/resources/supported-documents
A comprehensive list of documents supported by ID Check
ID Check supports over 2000 identity documents. See all [here](https://docs.google.com/spreadsheets/d/1RE5wImwjbTkLJegdmXEfZT4_55xIs-FNvti6bqylFoQ/edit?gid=223707290#gid=223707290)
# Verifications Overview
Source: https://docs.verisoul.ai/verifications/overview
Understanding and implementing Verisoul Verifications
## What Are Verifications?
Verisoul's verification products are additional security checks that provide an extra layer of security for high-risk accounts or transactions. This guide explains how verifications work and how to implement them in your application.
## Verification Products
Verisoul supports multiple verification products:
### Face Match
* **Facial Biometrics**: Verify user identity through facial recognition
* **Liveness Detection**: Ensure the verification is being performed by a real person
* **Uniqueness Checks**: Prevent multiple accounts using the same identity
* **Authentication**: Confirm a user is who they claim to be
### ID Check
* **Document Verification**: Validate government-issued identification documents
* **Liveness Detection**: Ensure the verification is being performed by a real person
* **Face-to-ID Matching**: Verify that the user's face matches the photo on their ID
* **Global Coverage**: Support for ID documents from multiple countries
## When to Use Verifications
Verifications can be triggered in several scenarios:
### Risk-Based Verification
Automatically trigger verifications when risk thresholds are exceeded:
* **Suspicious or Fake Accounts**: When Verisoul marks an account as Suspicious or Fake, prompt the user to complete an additional verification to confirm their identity
### Action-Based Verification
Require verification for specific high-value actions:
* **Account creation**: Verify identity during registration
* **Login from new devices**: Confirm it's the legitimate user
* **Password changes**: Prevent unauthorized account changes
* **Payment processing**: Verify identity before processing payments
* **Withdrawal requests**: Confirm identity before sending funds
### Manual Verification
Trigger verifications based on manual review:
* **Suspicious activity**: When a reviewer identifies concerning patterns
* **Customer support**: When helping users with sensitive account issues
* **Compliance requirements**: To meet regulatory obligations
## Next Steps
* Learn about [Face Match Integration](/verifications/face-match/integration/integration) for technical implementation details
* Learn about [ID Check Integration](/verifications/id-check/integration/integration) for technical implementation details
* See the [Example App](/examples/react-native-facematch-sample-app) for a complete implementation
# Webhooks
Source: https://docs.verisoul.ai/verifications/webhooks
Complete reference for the verification.face-match.completed and verification.id-check.completed webhook payloads
## Payload Structure
When a Face Match or ID Check session completes, Verisoul sends a `POST` request to your configured webhook endpoint. A webhook fires for every session outcome: pass, fail, or abandonment.
Fires when a Face Match session completes
Fires when an ID Check session completes
**If your flow has a client-side dependency** (the user is waiting in your UI for a pass/fail result), **await the [redirect](/verifications/face-match/integration/navigate-to-facematch#on-completion)**. It fires the moment the session completes.
**Otherwise, use the webhook.** It typically arrives within a few seconds of session completion, though delivery can slow during high-traffic bursts. It is the better fit for backend record-keeping and async processing.
You can also use both: await the redirect for the immediate result and treat the webhook as the durable record.
Every webhook Verisoul sends uses the same signing scheme, including these. See [Webhook Signature Verification](/email-intelligence/webhook-signature-verification) to validate the `x-signature` header before processing a payload.
***
## Example Success Payload: Face Match
On success, `data` is exactly what [`POST /liveness/verify-face`](/api-reference/face-match/verify-face) would return for the session.
```json theme={null}
{
"request_id": "fd4c7dc1-32f7-4dfb-99c2-a6daafeaad94",
"session_id": "000575be-fa64-4274-b810-89def4fce2f7",
"project_id": "ed8fe5d7-ead9-4b81-b1b0-74190582341c",
"event_type": "verification.face-match.completed",
"created_at": "2026-08-24T20:43:20.921Z",
"completed_at": "2026-08-24T20:44:04.246Z",
"status": "success",
"data": {
"metadata": {
"project_id": "ed8fe5d7-ead9-4b81-b1b0-74190582341c",
"session_id": "000575be-fa64-4274-b810-89def4fce2f7",
"account_id": null,
"referring_session_id": null,
"request_id": "fd4c7dc1-32f7-4dfb-99c2-a6daafeaad94",
"timestamp": "2026-08-24T20:43:30.655Z"
},
"decision": "Fake",
"risk_score": 1,
"risk_flags": ["repeat_face", "repeat_device"],
"lists": [],
"rules": [],
"device_network_signals": {
"device_risk": 0.4584,
"proxy": 0.4033,
"vpn": 0,
"datacenter": 0,
"tor": 0,
"spoofed_ip": 0,
"recent_fraud_ip": 0,
"device_network_mismatch": 0.0174,
"location_spoofing": 0
},
"referring_session_signals": {
"impossible_travel": 0,
"ip_mismatch": 0,
"user_agent_mismatch": 0,
"device_timezone_mismatch": 0,
"ip_timezone_mismatch": 0
},
"photo_urls": {
"face": "https://storage.googleapis.com/facematch-prod/000575be.../face.jpg?X-Goog-Signature=..."
},
"session_data": {
"true_country_code": "US",
"network": {
"ip_address": "203.0.113.42",
"service_provider": "Google Fiber Inc.",
"connection_type": "isp"
},
"location": {
"continent": "NA",
"country_code": "US",
"state": "Texas",
"city": "Austin",
"zip_code": "78701",
"timezone": "America/Chicago",
"latitude": 30.2672,
"longitude": -97.7431
},
"browser": {
"type": "Chrome",
"version": "150.0.7871.124",
"language": "en-US",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
"timezone": "America/Chicago"
},
"device": {
"category": "desktop",
"type": "Mac",
"os": "macOS 26.4.1",
"cpu_cores": 18,
"memory": 32,
"gpu": "ANGLE (Apple, ANGLE Metal Renderer: Apple M5 Pro, Unspecified Version)"
}
},
"matches": {
"num_accounts_linked": 3,
"accounts_linked": [
{ "account_id": "acct_sandbox_test", "match_types": ["face", "browser"] },
{ "account_id": "sandbox_test_2", "match_types": ["face", "browser"] },
{ "account_id": "ben-test-1", "match_types": ["face", "browser"] }
]
},
"video_urls": [
"https://storage.googleapis.com/facematch-prod/000575be.../session_video_1787604230036.webm?X-Goog-Signature=..."
]
},
"error": null
}
```
***
## Example Success Payload: ID Check
On success, `data` is exactly what [`POST /liveness/verify-id`](/api-reference/id-check/verify-id) would return for the session. It uses the same shape as Face Match plus `document_signals` and `document_data`; `photo_urls` nests inside `document_data` alongside the ID scan images.
```json theme={null}
{
"request_id": "dab2f1f9-4b49-4595-9635-64c02228306f",
"session_id": "00035b3e-5b99-4c40-ad07-178c154018ca",
"project_id": "ed8fe5d7-ead9-4b81-b1b0-74190582341c",
"event_type": "verification.id-check.completed",
"created_at": "2026-08-24T20:45:17.691Z",
"completed_at": "2026-08-24T20:48:50.489Z",
"status": "success",
"data": {
"metadata": {
"project_id": "ed8fe5d7-ead9-4b81-b1b0-74190582341c",
"session_id": "00035b3e-5b99-4c40-ad07-178c154018ca",
"account_id": null,
"referring_session_id": null,
"request_id": "dab2f1f9-4b49-4595-9635-64c02228306f",
"timestamp": "2026-08-24T20:45:29.923Z"
},
"decision": "Fake",
"risk_score": 1,
"risk_flags": ["moderate_id_face_match_score", "repeat_face", "repeat_id", "repeat_device"],
"document_signals": {
"id_age": 29,
"face_age_estimate": "over25",
"id_face_match_score": 0.45,
"id_barcode_status": "success",
"id_face_status": "likely_original_face",
"id_text_status": "likely_original_text",
"is_id_digital_spoof": "likely_physical_id",
"is_full_id_captured": "full_id_detected",
"id_validity": "likely_authentic_id"
},
"device_network_signals": {
"ip_document_country_mismatch": false,
"device_risk": false,
"proxy": false,
"vpn": false,
"datacenter": false,
"tor": false,
"spoofed_ip": false,
"recent_fraud_ip": false,
"device_network_mismatch": false
},
"referring_session_signals": {
"impossible_travel": false,
"ip_mismatch": false,
"user_agent_mismatch": false
},
"document_data": {
"template_info": {
"document_country_code": "US",
"document_country": "United States",
"document_state": "Massachusetts",
"template_type": "driver_license"
},
"user_data": {
"first_name": "Jane",
"last_name": "Doe",
"date_of_birth": "01/15/1995",
"date_of_expiration": "01/15/2029",
"date_of_issue": "01/10/2024",
"id_number": "X12345678",
"id_number2": null,
"address": {
"city": "Boston",
"country": "US",
"postal_code": "02108",
"state": "MA",
"street": "1 Main St"
}
},
"photo_urls": {
"face": "https://storage.googleapis.com/facematch-prod/00035b3e.../face.jpg?X-Goog-Signature=...",
"id_scan_back": "https://storage.googleapis.com/facematch-prod/00035b3e.../id_scan_back.jpg?X-Goog-Signature=...",
"id_scan_front": "https://storage.googleapis.com/facematch-prod/00035b3e.../id_scan_front.jpg?X-Goog-Signature=..."
}
},
"session_data": {
"network": {
"ip_address": "203.0.113.42",
"service_provider": "Google Fiber Inc.",
"connection_type": "isp"
},
"location": {
"continent": "NA",
"country_code": "US",
"state": "Texas",
"city": "Austin",
"zip_code": "78701",
"timezone": "America/Chicago",
"latitude": 30.2672,
"longitude": -97.7431
},
"browser": {
"type": "Chrome",
"version": "150.0.7871.124",
"language": "en-US",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
"timezone": "America/Chicago"
},
"device": {
"category": "desktop",
"type": "Mac",
"os": "macOS 26.4.1",
"cpu_cores": 18,
"memory": 32,
"gpu": "ANGLE (Apple, ANGLE Metal Renderer: Apple M5 Pro, Unspecified Version)"
}
},
"matches": {
"num_accounts_linked": 3,
"accounts_linked": [
{ "account_id": "acct_sandbox_test", "match_types": ["face", "browser"] },
{ "account_id": "sandbox_test_2", "match_types": ["face", "browser", "document_number", "document_name"] },
{ "account_id": "ben-test-1", "match_types": ["face", "browser", "document_number"] }
]
},
"video_urls": [
"https://storage.googleapis.com/facematch-prod/00035b3e.../session_video_1787604461595.webm?X-Goog-Signature=...",
"https://storage.googleapis.com/facematch-prod/00035b3e.../session_video_1787604521797.webm?X-Goog-Signature=..."
]
},
"error": null
}
```
Success payloads contain PII (name, date of birth, ID number, address for ID Check) and signed, time-limited URLs to photo/video evidence. URLs are signed at the moment the webhook is sent, so their expiration window starts when you receive them and lasts for 60 minutes. Treat the payload itself as sensitive data.
***
## Example Error Payload
```json theme={null}
{
"request_id": "67c334e0-b01c-427d-8425-d32c144a2e52",
"session_id": "00058a40-0b02-431f-9115-ee9983c10e13",
"project_id": "ed8fe5d7-ead9-4b81-b1b0-74190582341c",
"event_type": "verification.face-match.completed",
"created_at": "2026-08-18T20:13:26.762Z",
"completed_at": "2026-08-18T20:15:44.799Z",
"status": "error",
"data": null,
"error": {
"code": "face_match_failed",
"message": "The user failed the face match check after the maximum number of attempts"
}
}
```
***
## Field Reference
### Top-Level Fields
| Field | Type | Description |
| - | - | - |
| `request_id` | string | Unique identifier for this webhook. On success, matches `data.metadata.request_id`. |
| `session_id` | string | The verification session ID. Present on both success and error, so use it to correlate the webhook with your session. |
| `project_id` | string | Your Verisoul project ID |
| `event_type` | string | `verification.face-match.completed` or `verification.id-check.completed` |
| `created_at` | string | When the verification session was created (ISO 8601) |
| `completed_at` | string | When the session completed (ISO 8601) |
| `status` | string | `success` or `error` |
| `data` | object \| null | The full verification result (present on success) |
| `error` | object \| null | Error details (present on failure) |
### `data`
The full verification result, identical to the `verify-face` or `verify-id` API response. For field-by-field definitions, see:
* [Face Match Response Signals](/verifications/face-match/integration/response-signals)
* [ID Check Response Signals](/verifications/id-check/integration/response-signals)
### `error`
| Field | Type | Description |
| - | - | - |
| `code` | string | Error code (see [Error Codes](#error-codes) below) |
| `message` | string | Human-readable description of the failure |
***
## Error Codes
| Code | Meaning |
| - | - |
| `liveness_check_failed` | The session ended in a non-success state, for example the user cancelled a step. |
| `face_match_failed` | The face match/liveness step reached the maximum number of attempts without succeeding. |
| `failed_to_process_id_scan` | The identity document scan could not be processed (ID Check only). |
| `failed_to_collect_device_data` | Required device or network data could not be collected from the user's browser. |
| `max_duration_exceeded` | The session was never completed and can no longer be completed. Sessions stay completable for 30 days after creation, so this can arrive well after the user last interacted with the flow. |
The redirect's `error_message` vocabulary is broader than this list. `invalid_session_id` and `session_id_not_found` appear only in a [redirect](/verifications/face-match/integration/navigate-to-facematch#error-handling); a webhook fires only for sessions that were found and valid.
***
## Delivery Guarantees
* **Exactly once per session.** Each session fires exactly one webhook, for whichever terminal state it reaches first. This holds even if the user abandons the session or `verify-face`/`verify-id` is never called.
* **Signed.** Every payload includes an `x-signature` header. See [Webhook Signature Verification](/email-intelligence/webhook-signature-verification) for how to verify it.
* **Retried with backoff** if your endpoint returns a non-2xx response.