# Acknowledge alert
Source: https://docs.zenoo.com/api-reference/alerts/acknowledge-alert
/zenoo-api-openapi.yaml post /v1/alerts/{alert_token}/acknowledge
Acknowledge an alert to indicate it has been seen and is being investigated.
Changes the alert status from `Open` to `Acknowledged`.
# Assign alert
Source: https://docs.zenoo.com/api-reference/alerts/assign-alert
/zenoo-api-openapi.yaml post /v1/alerts/{alert_token}/assign
Assign an alert to a specific analyst for investigation. The assigned user
receives a notification.
# Bulk acknowledge alerts
Source: https://docs.zenoo.com/api-reference/alerts/bulk-acknowledge-alerts
/zenoo-api-openapi.yaml post /v1/alerts/bulk/acknowledge
Acknowledge multiple alerts in a single request. Maximum 50 alerts per request.
# Bulk assign alerts
Source: https://docs.zenoo.com/api-reference/alerts/bulk-assign-alerts
/zenoo-api-openapi.yaml post /v1/alerts/bulk/assign
Assign multiple alerts to a specific analyst. Maximum 50 alerts per request.
# Bulk resolve alerts
Source: https://docs.zenoo.com/api-reference/alerts/bulk-resolve-alerts
/zenoo-api-openapi.yaml post /v1/alerts/bulk/resolve
Resolve multiple alerts in a single request. All alerts must belong to the
same case. The same resolution action and rationale are applied to all alerts.
Maximum 50 alerts per request.
# Escalate alert
Source: https://docs.zenoo.com/api-reference/alerts/escalate-alert
/zenoo-api-openapi.yaml post /v1/alerts/{alert_token}/escalate
Escalate an alert to a senior reviewer. The alert status changes to `Escalated`
and the designated reviewer is notified.
# Get AI research results
Source: https://docs.zenoo.com/api-reference/alerts/get-ai-research-results
/zenoo-api-openapi.yaml get /v1/alerts/{alert_token}/ai-research
Retrieve AI research results for an alert. Returns the full analysis
including confidence scores, source citations, and recommended actions.
# Get alert details
Source: https://docs.zenoo.com/api-reference/alerts/get-alert-details
/zenoo-api-openapi.yaml get /v1/alerts/{alert_token}
Retrieve full details for a specific alert, including screening match data,
AI assessment, SLA status, and resolution history.
# List alerts
Source: https://docs.zenoo.com/api-reference/alerts/list-alerts
/zenoo-api-openapi.yaml get /v1/alerts
Retrieve alerts across all cases with optional filtering. Results are sorted
by priority score (highest first) by default.
# Resolve alert
Source: https://docs.zenoo.com/api-reference/alerts/resolve-alert
/zenoo-api-openapi.yaml post /v1/alerts/{alert_token}/resolve
Resolve an alert with a specified action and rationale. The resolution is
recorded in the audit trail and the alert status changes to `Resolved`
or `False Positive` depending on the action.
## Resolution Actions
| Action | Result Status | Use When |
|--------|--------------|----------|
| `Approve` | Resolved | True match, risk accepted |
| `Decline` | Resolved | True match, entity rejected |
| `Escalate` | Escalated | Needs senior review |
| `Request Document` | Open (awaiting) | Additional evidence needed |
| `Approve with Conditions` | Resolved | Conditional acceptance |
# Trigger AI research
Source: https://docs.zenoo.com/api-reference/alerts/trigger-ai-research
/zenoo-api-openapi.yaml post /v1/alerts/{alert_token}/ai-research
Initiate AI-powered research for an alert. The AI analyzes available data,
performs open-source intelligence gathering, and produces a structured
assessment with confidence scoring and recommended actions.
AI research runs asynchronously. Poll the GET endpoint or await the webhook
for results.
# Alert statistics
Source: https://docs.zenoo.com/api-reference/analytics/alert-statistics
/zenoo-api-openapi.yaml get /v1/analytics/alerts
Retrieve alert volume, resolution rates, average resolution time, and
category breakdown for the specified time period.
# Case statistics
Source: https://docs.zenoo.com/api-reference/analytics/case-statistics
/zenoo-api-openapi.yaml get /v1/analytics/cases
Retrieve case throughput, aging distribution, type breakdown, and risk tier
distribution for the specified time period.
# Summary metrics
Source: https://docs.zenoo.com/api-reference/analytics/summary-metrics
/zenoo-api-openapi.yaml get /v1/analytics/summary
Retrieve key compliance KPIs including open case count, pending alert count,
SLA health percentage, and average case resolution time. Metrics are
calculated in real-time.
# List audit logs
Source: https://docs.zenoo.com/api-reference/audit-trail/list-audit-logs
/zenoo-api-openapi.yaml get /v1/audit-logs
Query the immutable audit log with filtering by event type, actor, target,
and date range. Audit logs cannot be modified or deleted.
# Add comment to case
Source: https://docs.zenoo.com/api-reference/cases/add-comment-to-case
/zenoo-api-openapi.yaml post /v1/cases/{case_token}/comments
Add a comment to a case. Comments support `@mentions` for notifying team
members. Use `type: Decision` for formal compliance decisions that appear
in the audit trail.
# Add entity to case
Source: https://docs.zenoo.com/api-reference/cases/add-entity-to-case
/zenoo-api-openapi.yaml post /v1/cases/{case_token}/entities
Associate an existing entity with a case, or create a new entity and add it
in one step. Specify the entity's role within the case (Primary, UBO, Director,
Shareholder).
# Add reviewer to case
Source: https://docs.zenoo.com/api-reference/cases/add-reviewer-to-case
/zenoo-api-openapi.yaml post /v1/cases/{case_token}/reviewers
Add a reviewer to a case. The reviewer receives a notification and their
status is set to `Invited` until they begin their review.
# Close case
Source: https://docs.zenoo.com/api-reference/cases/close-case
/zenoo-api-openapi.yaml post /v1/cases/{case_token}/close
Close an investigation case. Requires all alerts to be resolved and a final
risk assessment to be approved. Optionally include a closing summary and
decision rationale.
Closing a case triggers the `case.closed` webhook event and creates an
immutable audit log entry.
# Create case
Source: https://docs.zenoo.com/api-reference/cases/create-case
/zenoo-api-openapi.yaml post /v1/cases
Create a new investigation case. A case is the top-level container for a
compliance investigation — it groups entities, checks, alerts, and risk
assessments into a single tracked unit.
Cases are created with status `New` and can optionally include an initial
entity and assigned reviewer.
# Create/recalculate risk assessment
Source: https://docs.zenoo.com/api-reference/cases/createrecalculate-risk-assessment
/zenoo-api-openapi.yaml post /v1/cases/{case_token}/risk-assessments
Calculate a new risk assessment for a case using the active risk model.
If an active assessment already exists, it is superseded by the new one.
The assessment evaluates four dimensions — customer, geographic,
product/service, and transaction — producing an overall score and tier.
# Escalate case
Source: https://docs.zenoo.com/api-reference/cases/escalate-case
/zenoo-api-openapi.yaml post /v1/cases/{case_token}/escalate
Escalate a case to a senior reviewer or compliance officer. Escalation changes
the case status to `Escalated` and notifies the designated escalation contact.
# Generate compliance report
Source: https://docs.zenoo.com/api-reference/cases/generate-compliance-report
/zenoo-api-openapi.yaml get /v1/cases/{case_token}/compliance-report
Generate a comprehensive compliance report for a case, including entity
details, check results, alert resolutions, risk assessment, and audit trail.
The report is suitable for regulatory submission and internal records.
# Get case audit trail
Source: https://docs.zenoo.com/api-reference/cases/get-case-audit-trail
/zenoo-api-openapi.yaml get /v1/cases/{case_token}/timeline
Retrieve the complete audit trail for a case, showing every action taken
in chronological order. This is the case-scoped view of the immutable
audit log.
# Get case details
Source: https://docs.zenoo.com/api-reference/cases/get-case-details
/zenoo-api-openapi.yaml get /v1/cases/{case_token}
Retrieve full details for a specific case, including status, risk tier,
SLA information, and summary counts of entities, alerts, and checks.
# Get risk assessment
Source: https://docs.zenoo.com/api-reference/cases/get-risk-assessment
/zenoo-api-openapi.yaml get /v1/cases/{case_token}/risk-assessment
Retrieve the current active risk assessment for a case. Returns the overall
score, tier classification, and dimension-level breakdown.
# List case alerts
Source: https://docs.zenoo.com/api-reference/cases/list-case-alerts
/zenoo-api-openapi.yaml get /v1/cases/{case_token}/alerts
Retrieve all alerts across all entities in a case. Alerts are sorted by
priority score (highest first), then by creation date.
# List case checks
Source: https://docs.zenoo.com/api-reference/cases/list-case-checks
/zenoo-api-openapi.yaml get /v1/cases/{case_token}/checks
Retrieve all verification checks across all entities in a case. Includes
check status, result, and provider information.
# List case comments
Source: https://docs.zenoo.com/api-reference/cases/list-case-comments
/zenoo-api-openapi.yaml get /v1/cases/{case_token}/comments
Retrieve all comments and discussion entries for a case, including analyst
notes, formal decisions, and system-generated events.
# List case entities
Source: https://docs.zenoo.com/api-reference/cases/list-case-entities
/zenoo-api-openapi.yaml get /v1/cases/{case_token}/entities
Retrieve all entities associated with a case, including their roles, verification
status, and summary check/alert counts.
# List case reviewers
Source: https://docs.zenoo.com/api-reference/cases/list-case-reviewers
/zenoo-api-openapi.yaml get /v1/cases/{case_token}/reviewers
Retrieve all reviewers assigned to a case, including their role and
review status.
# List cases
Source: https://docs.zenoo.com/api-reference/cases/list-cases
/zenoo-api-openapi.yaml get /v1/cases
Retrieve cases with optional filtering by status, type, entity category,
risk tier, or assigned reviewer. Results are sorted by creation date
(newest first) by default.
# Update case
Source: https://docs.zenoo.com/api-reference/cases/update-case
/zenoo-api-openapi.yaml patch /v1/cases/{case_token}
Update case attributes. Only provided fields are modified. Status transitions
must follow the valid lifecycle (e.g., cannot move from `Closed` to `New`).
# Execute check
Source: https://docs.zenoo.com/api-reference/checks/execute-check
/zenoo-api-openapi.yaml post /v1/checks/{check_token}/run
Trigger execution of a verification check. The check is queued and executed
against its configured provider. Results are delivered via the check status
endpoint or webhook.
Checks in `Pending` or `Failed` status can be executed. Checks in `Complete`
or `Waived` status cannot be re-run.
# Get check details
Source: https://docs.zenoo.com/api-reference/checks/get-check-details
/zenoo-api-openapi.yaml get /v1/checks/{check_token}
Retrieve full details for a verification check, including status, result,
provider information, and structured result data.
# Waive check
Source: https://docs.zenoo.com/api-reference/checks/waive-check
/zenoo-api-openapi.yaml post /v1/checks/{check_token}/waive
Waive a check with a documented reason. Waived checks are excluded from
the verification requirements but recorded in the audit trail for
regulatory transparency.
# Create person or company entity
Source: https://docs.zenoo.com/api-reference/entities/create-person-or-company-entity
/zenoo-api-openapi.yaml post /v1/entities
Create a new entity for compliance investigation. The entity type determines
which fields are required and which checks can be executed.
**Person entities** require `first_name`, `last_name`, and at least one of
`date_of_birth` or `nationality` for effective screening.
**Company entities** require `company_name` and `country`. Include
`registration_number` for registry lookups.
# Get entity details
Source: https://docs.zenoo.com/api-reference/entities/get-entity-details
/zenoo-api-openapi.yaml get /v1/entities/{entity_token}
Retrieve full details for a specific entity, including identifying information,
verification status, and summary counts of associated checks and alerts.
# List entities with filters
Source: https://docs.zenoo.com/api-reference/entities/list-entities-with-filters
/zenoo-api-openapi.yaml get /v1/entities
Retrieve entities with optional filtering by type, verification status,
or case association. Results are sorted by creation date (newest first).
# List entity alerts
Source: https://docs.zenoo.com/api-reference/entities/list-entity-alerts
/zenoo-api-openapi.yaml get /v1/entities/{entity_token}/alerts
Retrieve all alerts generated from checks on this entity. Includes screening
matches, verification failures, and system alerts.
# List entity checks
Source: https://docs.zenoo.com/api-reference/entities/list-entity-checks
/zenoo-api-openapi.yaml get /v1/entities/{entity_token}/checks
Retrieve all verification checks associated with an entity. Includes checks
from all cases the entity belongs to.
# Update entity
Source: https://docs.zenoo.com/api-reference/entities/update-entity
/zenoo-api-openapi.yaml patch /v1/entities/{entity_token}
Update entity attributes. Only provided fields are modified; omitted fields
retain their current values. Updating key identifying fields (name, DOB)
may trigger re-screening.
# Execute Company Verification (sync)
Source: https://docs.zenoo.com/api-reference/flow-api/execute-company-verification-sync
/zenoo-api-openapi.yaml post /api/gateway/execute/{project_hash}/api
Submit company data for synchronous registry lookup and screening. The API
performs company registry verification, director screening, and UBO screening
in a single call.
Include the `X-SYNC-TIMEOUT` header to control how long the API blocks before
returning. If checks complete within the timeout, a full `VerificationReport` is
returned (200). If the timeout is exceeded, partial results with pull/start
tokens are returned (202) so you can retrieve the rest asynchronously.
# Execute Person Verification (sync)
Source: https://docs.zenoo.com/api-reference/flow-api/execute-person-verification-sync
/zenoo-api-openapi.yaml post /api/gateway/execute/{project_hash}/kyc/api
Submit person data for synchronous screening and database verification. Use
this endpoint for server-side checks when you already have the individual's
data (name, date of birth, nationality, etc.).
Include `X-SYNC-TIMEOUT` to control blocking duration. Returns a full
`VerificationReport` on success (200) or partial results with tokens (202)
if the timeout is exceeded.
# Initiate Company Verification (async)
Source: https://docs.zenoo.com/api-reference/flow-api/initiate-company-verification-async
/zenoo-api-openapi.yaml post /api/gateway/execute/{project_hash}/init
Initiate asynchronous company verification. Returns tokens immediately without
waiting for checks to complete. Use the `pull` token to retrieve results via
`GET /api/gateway/sharable-payload/{pull}`, or configure a webhook for delivery.
# Initiate Person Verification journey (async)
Source: https://docs.zenoo.com/api-reference/flow-api/initiate-person-verification-journey-async
/zenoo-api-openapi.yaml post /api/gateway/execute/{project_hash}/kyc/init
Initialize an asynchronous person verification journey. Returns tokens
immediately. Use the `start` token to construct a verification URL that
redirects the individual to Zenoo's hosted verification experience for
document capture and biometric liveness.
**Verification URL format:**
```
https://instance.prod.onboardapp.io/{project_hash}/?t={start_token}
```
The `start` token expires after 24 hours. The `pull` token remains valid
for 30 days after results become available.
# Retrieve async verification results
Source: https://docs.zenoo.com/api-reference/flow-api/retrieve-async-verification-results
/zenoo-api-openapi.yaml get /api/gateway/sharable-payload/{pull_token}
Retrieve results for an asynchronous verification using the `pull` token
obtained from any `/init` endpoint or from a 202 response on a sync endpoint.
## Polling Strategy
```
First 2 minutes → poll every 10 seconds
After 2 minutes → poll every 30 seconds
Maximum → 10 minutes total
```
A `204 No Content` response means the verification is still processing.
Continue polling according to the schedule above.
> **Prefer webhooks over polling.** Configure a webhook in Zenoo Studio to
> receive results automatically when processing completes.
# Screen entity for PEP/sanctions/adverse media
Source: https://docs.zenoo.com/api-reference/flow-api/screen-entity-for-pepsanctionsadverse-media
/zenoo-api-openapi.yaml post /api/gateway/execute/{project_hash}/screening/api
Perform standalone AML screening against PEP, sanctions, adverse media, and
watchlist databases. Supports both person and company entities.
This endpoint is for screening only — it does not perform registry lookups,
identity verification, or document checks. Use the company or person
verification endpoints for full compliance checks.
# API Reference
Source: https://docs.zenoo.com/api-reference/introduction
Two APIs, one platform — orchestrate verification flows and manage compliance workflows programmatically.
Zenoo gives you two complementary APIs. **Flow API** orchestrates verification journeys across multiple providers. **Case Management API** gives you programmatic control over the compliance workspace where your team reviews results.
Both are REST, both use JSON, both authenticate with `X-API-KEY`.
***
## Choose your API
Orchestrate KYC, KYB, and screening checks across providers through a single endpoint. Submit data, Zenoo runs the checks, you get consolidated results back.
Build on top of the compliance workspace — create cases, triage alerts, run risk assessments, and automate review workflows.
***
## Flow API
The Flow API is how you run verification checks. Instead of integrating with each provider separately, you send data to Zenoo and get a single consolidated response — company registry data, PEP/sanctions screening, document verification, and biometrics all in one call.
**Base URL**
```
https://{instance}.prod.onboardapp.io/api/gateway/execute/{project_hash}
```
### Endpoints
| Endpoint | Method | Use case |
| - | - | - |
| `/api` | POST | Run a company flow synchronously |
| `/init` | POST | Start a company flow asynchronously — returns a token pair |
| `/kyc/api` | POST | Run a person flow synchronously |
| `/kyc/init` | POST | Start a person flow asynchronously — returns a token pair |
| `/screening/api` | POST | Run PEP, sanctions, and adverse media screening |
| `/sharable-payload/{pull_token}` | GET | Retrieve results using a pull token |
### Two execution modes
Send data, wait for results. Best for server-to-server integrations where you can hold a connection open.
```bash theme={null}
curl -X POST ".../api" \
-H "X-API-KEY: your-api-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-H "Content-Type: application/json" \
-d '{"company_name": "Acme Ltd", "registration_number": "12345678", "country": "GB"}'
```
Returns the full compliance report directly. If checks don't finish within the timeout, you get a `202` with a pull token to retrieve results later.
Start a journey, get a token pair, retrieve results when ready. Best for user-facing flows with document capture or biometrics.
**1. Get tokens**
```bash theme={null}
curl -X POST ".../init" \
-H "X-API-KEY: your-api-key" \
-H "Content-Type: application/json" \
-d '{"first_name": "Jane", "last_name": "Doe"}'
```
```json theme={null}
{ "tokens": { "start": "bwTX29", "pull": "MHqPED" } }
```
**2. Send user to verification**
```
https://onboard.prod.zenoo.io/{project_id}/?t=bwTX29
```
**3. Retrieve results**
```bash theme={null}
curl ".../sharable-payload/MHqPED" -H "X-API-KEY: your-api-key"
```
### Quick example
```bash cURL theme={null}
curl -X POST "https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "X-API-KEY: your-api-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-H "Content-Type: application/json" \
-d '{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB"
}'
```
```python Python theme={null}
import requests
response = requests.post(
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/api",
headers={
"X-API-KEY": "your-api-key",
"X-SYNC-TIMEOUT": "30000",
"Content-Type": "application/json"
},
json={
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB"
}
)
report = response.json()
```
```javascript Node.js theme={null}
const response = await fetch(
`https://instance.prod.onboardapp.io/api/gateway/execute/${projectHash}/api`,
{
method: "POST",
headers: {
"X-API-KEY": "your-api-key",
"X-SYNC-TIMEOUT": "30000",
"Content-Type": "application/json"
},
body: JSON.stringify({
company_name: "Acme Holdings Ltd",
registration_number: "12345678",
country: "GB"
})
}
);
const report = await response.json();
```
```json 200 — Compliance report theme={null}
{
"verdict": "Refer",
"risk_tier": "Medium",
"company": {
"name": "Acme Holdings Ltd",
"registration_number": "12345678",
"status": "Active",
"jurisdiction": "GB",
"directors": [
{ "name": "Jane Smith", "role": "Director", "appointed": "2019-03-15" }
]
},
"screening": {
"total_hits": 2,
"pep_matches": 1,
"sanctions_matches": 0,
"adverse_media_matches": 1
},
"risk_assessment": {
"overall_tier": "Medium",
"customer_risk": 45,
"country_risk": 20,
"product_risk": 35,
"channel_risk": 10
}
}
```
***
## Case Management API
The Case Management API gives you programmatic access to the compliance workspace. Use it to automate review workflows, integrate with internal systems, or build custom compliance dashboards.
**Base URL**
```
https://api.zenoo.com/v1
```
### Resources
**6 endpoints** — Create and manage the people and companies being verified. Each entity has checks, alerts, and documents attached.
**18 endpoints** — Full case lifecycle from creation to closure. Add entities, manage reviewers, track timelines, generate compliance reports.
**11 endpoints** — Triage, resolve, escalate, and bulk-manage compliance alerts. Includes AI-powered research for screening matches.
**3 endpoints** — View check results, re-run failed checks, or waive requirements when appropriate.
**3 endpoints** — View 4-dimension risk scores, approve assessments, or override with justification.
**4 endpoints** — Immutable audit logs and aggregated analytics for compliance reporting.
### What you can build
* **Automated triage** — Route new alerts to the right team based on category, risk tier, or entity type
* **Custom dashboards** — Pull case and alert analytics into your BI tools
* **Bulk operations** — Resolve, acknowledge, or assign hundreds of alerts in a single call
* **Workflow integration** — Trigger actions in Slack, Jira, or internal tools when cases change status
* **Compliance reporting** — Generate audit trails and SLA reports from the API
The Case Management API is documented with fully defined schemas in the OpenAPI spec. Use the sidebar to explore each resource.
***
## Authentication
Every request requires your API key:
```
X-API-KEY: your-api-key
```
Keys are project-scoped. Each project has separate **Sandbox** and **Production** keys. See [Authentication](/getting-started/authentication) for setup.
## Rate limits
| API | Limit | Burst |
| - | - | - |
| Flow sync (`/api`) | 100 req/min | 20 |
| Flow async (`/init`) | 100 req/min | 20 |
| Result polling (`/sharable-payload`) | 300 req/min | 50 |
| Screening (`/screening/api`) | 200 req/min | 30 |
| Case Management (`/v1/*`) | 500 req/min | 50 |
Rate limit headers are included in every response. See [Rate Limits](/reference/rate-limits) for retry strategies.
## Need help?
Authentication, environments, and your first API call
Error codes, retry strategies, and troubleshooting
# Approve risk assessment
Source: https://docs.zenoo.com/api-reference/risk-assessments/approve-risk-assessment
/zenoo-api-openapi.yaml post /v1/risk-assessments/{assessment_token}/approve
Approve a risk assessment. Only assessments in `Active` status can be approved.
Approval locks the assessment and records the approver in the audit trail.
# Get risk assessment
Source: https://docs.zenoo.com/api-reference/risk-assessments/get-risk-assessment
/zenoo-api-openapi.yaml get /v1/risk-assessments/{assessment_token}
Retrieve a specific risk assessment by token. Returns the full scoring
breakdown across all four dimensions plus the overall score and tier.
# Override risk tier
Source: https://docs.zenoo.com/api-reference/risk-assessments/override-risk-tier
/zenoo-api-openapi.yaml post /v1/risk-assessments/{assessment_token}/override
Override the calculated risk tier with a manual classification. Requires
a justification. The original calculated score is preserved alongside the
override for audit purposes.
# List webhooks
Source: https://docs.zenoo.com/api-reference/webhooks/list-webhooks
/zenoo-api-openapi.yaml get /v1/webhooks
Retrieve all registered webhook endpoints and their current status.
# Register webhook
Source: https://docs.zenoo.com/api-reference/webhooks/register-webhook
/zenoo-api-openapi.yaml post /v1/webhooks
Register a new webhook endpoint to receive event notifications. The endpoint
must be HTTPS and respond to a verification challenge before activation.
After registration, a webhook secret is returned for signature verification.
Store this secret securely — it cannot be retrieved again.
# Remove webhook
Source: https://docs.zenoo.com/api-reference/webhooks/remove-webhook
/zenoo-api-openapi.yaml delete /v1/webhooks/{webhook_id}
Remove a registered webhook endpoint. Events will no longer be delivered
to this endpoint after removal.
# February 2023
Source: https://docs.zenoo.com/changelog/2023-02
Key updates this month focused on platform infrastructure improvements, Studio workflow enhancements, and Compliance Hub document management capabilities.
## Platform
* **Execute Target Functions**: Added new execute target event system for improved function execution and refactoring
* **Component Configuration**: Introduced component builder with configuration support for more flexible component development
* **Multi-Instance Support**: Added ability to run multiple hub instances with Docker Compose replacing test containers
* **DSL Validation**: Enhanced component DSL with name uniqueness validation to prevent conflicts
* **Request Resolution**: Implemented execute request resolver for better request handling
## Studio
* **Project Overview**: New project overview page with improved navigation and project management
* **Target Deployment**: Split stage and production target deployment for better environment control
* **Workflow Branching**: Added target branching support with fixes for long URIs and validation improvements
* **Terms & Conditions**: Implemented TAC agreement button with URL validation and styling
* **Radio Button UI**: Enhanced radio button interface with improved styling and interaction patterns
* **Test Infrastructure**: Enabled Cypress tests and configured Mochawesome reporter for better test coverage
* **URI Generation**: Improved URI format generation with fixes for short titles and duplicate prevention
* **Field Validation**: Added "numbers only" validation option and improved contact details interaction
## Client SDK
* **Acuant SDK Update**: Upgraded Acuant SDK to version 11.8.0 for improved identity verification
* **Text Editing**: Added new line support to text editor component
* **Custom Select**: Fixed search functionality and improved custom select component behavior
## Compliance Hub
* **Document Management**: Bulkified ContentVersion service for improved performance with document retrieval
* **OCR & Biometric Data**: Added additional OCR and visual authenticity parameters with biometric card improvements
* **Automation Rules**: Fixed validation to prevent simultaneous approve and reject actions in automation rules
* **Settings UI**: Added section and order fields to settings layout with improved organization
* **Data Validation**: Fixed missing data issues on data validation table and identity component behavior
* **Email Templates**: Fixed User.Firstname population in confirmation emails
* **Sanctions List**: Replaced hardcoded texts with labels for better internationalization support
# March 2023
Source: https://docs.zenoo.com/changelog/2023-03
March brought significant enhancements to workflow execution, error handling, and API exposure capabilities across the platform.
## Platform
* **Exposed Functions**: Added exposed keyword to DSL enabling functions to be executed directly through API endpoints
* **Workflow Rewinding**: Implemented workflow rewind capability allowing users to step back and retry from previous points
* **Error Handling**: Added onError() handler for better exception management in workflows
* **File Cache**: Introduced file cache support for HTTP connector to improve performance with large files
* **Sharable Tokens**: Enhanced sharable tokens to store both execution context and payload data
* **HTTP Status Codes**: Improved execute function API responses with differentiated HTTP status codes for better error clarity
* **Local Run Plugin**: Published local-run plugin for easier local development and testing
## Studio
* **Cognito Integration**: Implemented remote sign-up with refactored credentials storage and improved error handling
* **Demo Environment**: Added new demo Amplify environment with deployment scripts and configuration
* **Brand Customization**: Added support for Prove and new Zenoo brands with SVG color theming
* **Component Testing**: Implemented snapshot component tests for regression prevention
* **Overview Page**: Added reload interval functionality to overview page for real-time updates
* **MinAge Parameter**: Fixed parameter passing for age validation in verification flows
* **Country Handling**: Added validation check for empty country when creating new projects
## Client SDK
* **File Upload**: Changed default behavior of file upload components with improved loading states
* **Idemia Integration**: Enhanced IdemiaFileUpload component with better visual feedback
* **Select Box**: Fixed broken placeholder display in custom select components
## Compliance Hub
* **Risk Score Engine**: Implemented complete risk score calculation framework with data model, calculator classes, and parameter configuration UI
* **Automation Rules**: Added automatic approval for Identity and Address verification types
* **Verification Management**: Created bulk verification creation scripts and improved verification opening
* **Dashboard Updates**: Updated reports and dashboards with new risk score metrics
* **ContentVersion Handling**: Enhanced ContentVersion trigger to run on UPDATE events
* **Data Validation**: Fixed data validation issues and error handling when opening verifications
* **Zenoo Referral App**: Updated with new Home Tab functionality
# April 2023
Source: https://docs.zenoo.com/changelog/2023-04
April delivered enhanced authentication, improved error handling, and significant field mapping capabilities for complex data integrations.
## Platform
* **Authentication**: Added authentication support for targets and exposed functions with improved security controls
* **Exchange Error Handler**: Implemented comprehensive error handler for exchange operations
* **Decision Handlers**: Added decision handler framework for conditional workflow logic
* **Connector Metrics**: Introduced metrics and health monitoring for connectors
* **Redis Cluster**: Added support for Redis cluster deployments for improved scalability
* **Current Route Query**: Fixed query handling for current route determination
* **Integration Testing**: Improved integration test startup and parallel execution
## Studio
* **Field Mapping**: Released new field mapping interface with key-value mapper and regex improvements
* **API Pull Interaction**: Added API pull interaction for external data retrieval
* **DO Init**: Implemented DO initialization workflow for Digital Ocean integrations
* **Target Access**: Added target access data storage in meta.json for deployment configuration
* **Xray Integration**: Integrated Xray for enhanced testing and quality assurance
* **Cookie Storage**: Refactored cookie storage for better session management
* **Country Code Prefix**: Added country code prefix handling for international phone numbers
* **GTM Support**: Added Google Tag Manager to demo environment
* **Remote Sign-up**: Fixed email case sensitivity in remote sign-up flow
## Client SDK
* **Acuant Enhancements**: Multiple improvements to Acuant SDK including camera fixes, cleanup methods, and loading states
* **Iovation**: Added clear on unmount for Iovation component to prevent memory leaks
* **Phone Formatting**: Added formatting for phone country codes
* **Analytics**: Added global analytics params to change page action tracking
* **Image Handling**: Fixed image rotation issues and back button handling for camera components
* **Sharable Token**: Added shortcut support for sharable token operations
## Compliance Hub
* **Risk Score Display**: Implemented UI components to display total risk score and variable risk scores with progress bar visualization
* **Field Mapping**: Updated field mapping to use global picklist value sets with improved data handling
* **Lightning Message Channel**: Replaced existing channel with new one configured with isExposed=false for security
* **Automation Rules**: Enhanced automation rule viewing for active rules
* **Date Formatting**: Fixed DOB field display in Identity component and verification list
* **Variable Mappings**: Added variable mappings content tabs for better configuration management
* **Twilio Integration**: Updated Twilio endpoint values in repository
# May 2023
Source: https://docs.zenoo.com/changelog/2023-05
May focused on enhancing execution tracing, improving HTTP handling, and delivering significant translation and user management improvements.
## Platform
* **Execution Tracing**: Implemented comprehensive execution tracing for better debugging and monitoring
* **HTTP Enhancements**: Added error logging, binary body support, and bearer authentication for HTTP connector
* **Spring Boot Upgrade**: Upgraded to Spring Boot 2.7.11 for improved performance and security
* **Redis Prefix Support**: Added prefix support for Redis to enable multi-tenancy
* **Rewind Fixes**: Fixed rewind functionality in sub-workflows for better error recovery
* **Topic Creation**: Made topic creation configurable for flexible Kafka setup
* **JSON Logging**: Added logback JSON appender for structured logging
* **Component Tester**: Fixed component tester issues and improved reliability
* **CORS**: Removed authentication from HTTP OPTIONS calls for better CORS support
## Studio
* **User Management**: Improved user management with library access controls and permission refactoring
* **Translation Support**: Added translation fallback for missing translations in Design Editor with legacy support
* **API Init**: Fixed production deployment and API init preview for smoother deployments
* **Project Management**: Enabled copy-paste into project name field for easier project creation
* **Back Button**: Fixed back button alignment for desktop users
* **Group Management**: Added Refinitiv group to production environment
* **Deployment Fixes**: Resolved flow deployment issues for production environment
* **Modal Improvements**: Fixed scroll behavior on modal content
* **Jest Tests**: Fixed component tests for better reliability
## Client SDK
* **Translation Enhancements**: Added translation support in index.yml with empty translation fallback
* **Acuant Improvements**: Added manual rotation, config path management, and iOS compatibility fixes
* **Date Format**: Implemented ISO date format for Datepicker component
* **Favicon Support**: Added favicon URL preparation and path fixes
* **Flow Configuration**: Added flowSharableRequired configuration option
* **Iovation**: Fixed global window property handling to prevent conflicts
## Compliance Hub
* **Risk Assessment Models**: Added columns, view actions, and update functionality for risk assessment models
* **Verification Status**: Fixed spinner issues, added status display, and improved list views
* **Content Management**: Fixed ContentDocumentLink ShareType and ContentVersion status handling
* **Variable Mapping**: Fixed developer name creation for variable mapping records
* **Event Logging**: Added event logging settings and updated email templates
* **Field History**: Enabled field history tracking for Verification Status
* **Setup Scripts**: Updated setup.sh to use new DevHub configuration
# June 2023
Source: https://docs.zenoo.com/changelog/2023-06
June introduced sharable token expiration, AWS Fargate deployment support, and significant Prove integration enhancements.
## Platform
* **Sharable Expiration**: Implemented automatic sharable token expiration with TTL support and cleanup
* **AWS Fargate**: Added complete AWS Fargate deployment configuration with CloudWatch logging
* **Await Attribute**: Added await attribute for synchronous operation handling
* **HTTP Improvements**: Added null removal in HTTP connector and bearer authentication support
* **Component Configuration**: Implemented fallback configuration for components
* **List Support**: Added list support for attributes enabling array operations
* **Latest Revision**: Enabled sharables to use latest component revision automatically
* **Function Results**: Enhanced function result responses with decision information
## Studio
* **Prove Integration**: Added Trust Score and Identity PreFill applications with complete Prove interaction set
* **JSON Field Mapping**: Implemented JSON field mapping for complex data structures
* **START Token**: Made START token always point to latest workflow version for consistency
* **Library Management**: Introduced Demo EU library with interaction prefixes and dependency management
* **Permissions**: Refactored permissions system for better access control
* **Translation Fixes**: Addressed translation issues with legacy support and null field handling
* **Cypress**: Added Cypress init logging and removed demo template
* **API Init Testing**: Implemented comprehensive API init tests
* **Target Management**: Added automatic removal of all target environments when removing target
## Client SDK
* **PR Checklist**: Introduced pull request checklist for better code review process
* **Translation**: Fixed empty translation fallback and added refresh of expressions
* **Date Handling**: Improved ISO date format handling with backward compatibility
* **Favicon**: Prevented loading favicon.ico for targets in subfolders
* **ResendCode**: Added loader to ResendCode component for better UX
## Compliance Hub
* **Risk Assessment UI**: Improved risk assessment model definition UI with label display and validation
* **Verification Check**: Implemented verification check component with JSON data display
* **Verification Tree**: Added verification related data tree for hierarchical view
* **Counter Management**: Implemented counter increment for used verification checks in custom settings
* **Date Display**: Added Declined Date field and updated Approved Date format
* **CRUD Operations**: Fixed missing delete check and added where clause to verification requirement queries
* **Automation Rules**: Made automation rule details visible in view mode
# July 2023
Source: https://docs.zenoo.com/changelog/2023-07
July delivered execution optimization improvements, enhanced permissions, and comprehensive Prove integration workflows.
## Platform
* **Execution Optimization**: Optimized exchange processor for better performance with configurable max request size
* **Child Executions**: Implemented sync executions as child executions for better tracking
* **Context Enhancement**: Added request headers and context to target and exposed functions
* **Sharable Propagation**: Fixed sharable result propagation and request context handling
* **Test Stability**: Improved integration test stability with better parallel execution handling
* **Fargate Configuration**: Updated Fargate configuration with improved logging and DNS settings
## Studio
* **Prove Instant Link**: Added Prove Instant Link interaction for real-time identity verification
* **CompareData Interaction**: Implemented CompareData interaction for data validation workflows
* **Webhook Interaction**: Added Webhook interaction to default library for external integrations
* **User Permissions**: Enhanced user permissions in Design Editor with admin permission mapping
* **Library Management**: Moved Refinitiv interactions to dedicated library with required library support
* **Static Mapping**: Enabled static mapping by default for faster field mapping
* **Refresh Token**: Implemented token refresh mechanism for better session management
* **Document Type Select**: Added document type selection interaction
* **Prefill Data**: Implemented data prefill with mappings for improved UX
* **Logo Improvements**: Fixed logo size and position in templates
* **Camera Settings**: Added dev camera cookie support to skip actual camera in development
* **Chart.js**: Fixed tooltip positioning in charts
* **Cypress Tests**: Improved Cypress test reliability and coverage
## Client SDK
* **Static Pages**: Added support for static pages with headless function execution
* **Async Results**: Implemented async result handling from Hub
* **Sharable Token**: Added flow reference token for reloading flows
* **Smartlook Integration**: Integrated Smartlook for user session recording and analytics
* **Datepicker**: Fixed date format handling without ISO format requirement
* **Console Feed**: Used forked console-feed module for better debugging
## Compliance Hub
* **Preview Images**: Added preview image option for document verification
* **Sharing Updates**: Updated share type configurations for better security
* **Future Methods**: Fixed future method calling restrictions from batch methods
* **Security Helper**: Resolved SecurityHelper access issues for Zenoo Org Data
* **Risk Model Saving**: Fixed ability to save risk model after corrections
* **Storage Management**: Implemented storage check scheduling in post-installation
* **Validation Scripts**: Updated Compliance Hub validation scripts for QA environment
* **Multiselect Validation**: Added duplicity warning for multiselect definitions
* **Risk Entity**: Fixed risk entity editability in view mode
# August 2023
Source: https://docs.zenoo.com/changelog/2023-08
August brought major testing and mocking capabilities, analytics enhancements, and Prove template improvements.
## Platform
* **Mocking Support**: Introduced comprehensive mocking framework for component testing
* **Connector Config**: Added connector configuration support for flexible integration setup
* **Await Timeout**: Implemented timeout functionality for await operations
* **Local Path Reference**: Added local path reference capability for file operations
* **Execution Events**: Refactored execution events with MDC logging for better traceability
* **Date Validator**: Added date validator with default ISO and custom pattern support
* **Component Tester**: Fixed component tester issues for more reliable testing
* **Request Context**: Improved request-context MDC and execution terminal result handling
## Studio
* **Prove Template**: Released complete Prove template with Trust Score and Instant Link integrations
* **Analytics Events**: Implemented analytics event tracking for user behavior insights
* **Template Tests**: Added comprehensive template testing framework
* **Mobile Verification**: Fixed mobile verification interaction with improved validation settings
* **Test Separation**: Separated API and UI tests for better test organization
* **Block Leave Handling**: Improved navigation blocking when users attempt to leave unsaved changes
* **Acuant SDK**: Fixed loading issues with Acuant SDK and script paths
* **Translation Processing**: Fixed translation processing with editable translations in mobile verification
* **Library Highlighting**: Added library highlighting for users to identify available libraries
* **Phone Validation**: Aligned validation settings for number and mobile number fields
## Client SDK
* **Static Page Handling**: Fixed sharable token checking on static pages
* **Default Values**: Prevented default values from being applied to visited fields inappropriately
## Compliance Hub
* **Verification Document**: Updated verification document selector for better query performance
* **Data Retries**: Added maximum verification data retries parameter to URL configuration
* **Test Coverage**: Fixed and improved test coverage across verification components
# September 2023
Source: https://docs.zenoo.com/changelog/2023-09
September focused on native component support, target caching improvements, and enhanced workflow validation.
## Platform
* **Native Components**: Added support for native hub components with Avro schema
* **Execution Snapshot**: Optimized execution snapshot for better performance
* **Workflow Validation**: Fixed workflow DSL validation for path cycle detection
* **Revision Resolution**: Fixed revision resolution for native components
* **ECS Secure Strings**: Added AWS ECS secure string support for better configuration management
* **Component Logging**: Changed component not found to log with WARN level instead of ERROR
* **Test Builder**: Fixed TestBuilder for improved testing reliability
* **Local Hub**: Fixed mockUrl for local hub instances
## Studio
* **Target Caching**: Improved targets cache usage for better performance
* **Date of Birth Mapping**: Enhanced mappings for date of birth fields
* **Logo Resize**: Added logo slider resize functionality
* **JWT Webhooks**: Implemented JWT support in webhooks for secure integrations
* **Region Selection**: Changed default region to Europe and added US region dropdown
* **API Credentials**: Fixed typos in API credentials handling
* **Error Outputs**: Added error output handling on applications
* **Documentation**: Added comprehensive documentation content
* **Clean Temp Folder**: Introduced action to clean temporary folder automatically
* **Naming Convention**: Implemented standardized naming convention across projects
* **Test Timeout**: Added 30-second timeout for end-to-end tests against test-eu environment
* **Library Build**: Fixed crash when watching library builds
* **Partner Name**: Added fallback partner name handling
## Client SDK
* **Acuant SDK**: Migrated to Acuant SDK 11.9.0 with improved file handling
* **File Upload**: Added cleanup on file change and onClick support for form buttons
* **File Naming**: Added ability to change file names in upload components
* **Idemia SDK**: Updated deprecated methods with connectivity checks
* **Validation Schema**: Fixed validation schema compilation before page mount
* **GTM Events**: Removed duplicated change page events for Google Tag Manager
## Compliance Hub
No significant changes this month.
# October 2023
Source: https://docs.zenoo.com/changelog/2023-10
October introduced Datadog logging, US region support, and enhanced authentication for string components.
## Platform
* **Datadog Integration**: Switched to Datadog US region for centralized logging and monitoring
* **String Component Auth**: Implemented authentication token storage for string components
* **Component Export**: Added export functionality for all string components to JSON file
* **Attribute Timeout**: Enabled expireIn and await timeout to be set from attributes
* **Component Config Merge**: Implemented merging of component default and given configuration
* **Registration Cache**: Fixed registration cache issues for better reliability
* **Revision Generator**: Fixed revision generator for proper version management
* **Back Navigation**: Fixed backEnabled functionality at first route after rewind
* **Component Definition**: Added component definition to query API response
* **Execution Context**: Removed terminated child execution context for cleanup
## Studio
* **US Region Support**: Added production and staging US region configurations
* **TTL Support**: Implemented TTL support for sharable tokens including start and pull tokens
* **Code Verification**: Added code verification interaction for SMS/email codes
* **Type Configuration**: Introduced type configuration system for better type management
* **Clone Target**: Fixed clone target modal issues
* **Questionnaire**: Fixed questionnaire interaction bugs
* **TAC Conditions**: Fixed issue with terms and conditions being unchecked when reverting versions
* **Connector Renaming**: Renamed connectors for better clarity
* **Region Display**: Added proper region display in sidebar
* **Target Filtering**: Added filter for load more targets functionality
* **Memory Optimization**: Increased memory to 2GB for test-eu and stage-eu environments
* **Preview Device**: Fixed preview device rendering issues
* **Back Button**: Fixed back button on Acuant upload interaction
* **Base Logo**: Fixed logo positioning for single column layouts
## Client SDK
* **Validation Order**: Updated compilation order for validation schema
* **Checkbox Group**: Added checkbox group component for multiple selections
* **Slider Events**: Added onMouseUp event handler to Slider component
* **Image Ordering**: Correctly handled order of captured images
* **Value Width**: Fixed error with value width calculation
* **Circular Dependencies**: Removed circular dependency between utils and translations
## Compliance Hub
Initial repository commits for new Compliance Hub development.
# November 2023
Source: https://docs.zenoo.com/changelog/2023-11
November introduced comprehensive execution metrics, enhanced testing frameworks, and significant user onboarding improvements.
## Platform
* **Execution Metrics**: Implemented comprehensive execution lifecycle metrics with decision tags and current route tracking
* **HTTP Retry Logic**: Enhanced HttpConnector to retry only on selected response status codes
* **Context Metrics**: Added execution context metrics for better performance monitoring
* **Component Query API**: Reverted component query API and added separate component definition query API
* **PII Protection**: Cleaned INFO logs from personally identifiable information
* **TestBuilder**: Fixed TestBuilder support for mappers and contexts
* **Request Context**: Fixed request context MDC setting for proper logging
## Studio
* **BV Applications**: Added new BV (Bank Verification) applications
* **GIACT Apps**: Updated input fields for GIACT applications
* **Onboarding**: Added complete onboarding flow to static files with lead checking
* **User Management**: Set current user partner to newly invited users by default
* **Target Cloning**: Fixed target groups on clone operation
* **Branch Creation**: Fixed branch creation issues
* **TTL Tokens**: Added comprehensive tests for TTL tokens
* **Build Hash**: Implemented build hash for force reload on new version releases
* **Acuant Upload**: Fixed error messages and multiple file upload handling
* **Password Reset**: Added validation to SetNewPassword modal
* **Extract Branding**: Added extract branding tests
* **Test Groups**: Restored test groups for test-eu environment
* **Timeout Improvements**: Increased timeout to show success notifications reliably
* **AWS Credentials**: Fixed AWS credentials for dev-eu deployment
## Client SDK
* **API v1 Migration**: Migrated API v1 to new /execute/ init call
* **Language Support**: Added new set of languages with locales
* **Strict Mode**: Added support for strict: false mode to pass builds with certain errors
* **GA Provider**: Added custom event calls for Google Analytics provider
* **Flow Functions**: Fixed flow function results handling
* **Idemia SDK**: Updated action order in change events
## Compliance Hub
No significant changes this month.
# December 2023
Source: https://docs.zenoo.com/changelog/2023-12
December closed the year with DynamoDB provisioning, enhanced security features, and comprehensive registration improvements.
## Platform
* **Component Usage**: Implemented component usage tracking for better insights
* **DynamoDB Provisioning**: Added automatic provisioning of DynamoDB tables for scalability
* **Forced Schedulers**: Implemented stream threads configuration with forced schedulers
* **Execution Logging**: Added enable-execution-log profile for debug scenarios
* **PII Protection**: Moved payload logging to trace level only to protect sensitive data
* **JsonView**: Removed JsonView for simplified JSON handling
## Studio
* **Registration Flow**: Complete overhaul of registration process with token-based authentication instead of email/temp password
* **User Onboarding**: Rebuilt onboarding experience with improved validation and lead management
* **Google Login**: Fixed Google authentication integration
* **API Init**: Automatically invoke API init for newly created targets during registration
* **Active Tab Groups**: Correctly set active tab group for new users
* **Library Management**: Safely check available libraries for users with old accounts
* **Password Modal**: Updated SetNewPassword modal design for better UX
* **Analytics Handling**: Properly handle analytics:null configuration in index.yml
* **Acuant Camera**: Fixed camera upload functionality in Acuant interaction
* **Field Mapper**: Updated styles in key-value field mapper for better readability
* **Search Query**: Correctly clear search query parameters after use
* **Custom Attributes**: Fixed custom company name attribute handling
* **Demo Templates**: Fixed demo\_loan template configuration
* **Sign-in Links**: Fixed sign-in link in onboarding flow
* **Terms & Conditions**: Removed T\&C toggle from registration for streamlined flow
## Client SDK
* **Idemia Enhancements**: Added optional identityId support and session finishing after capture
* **Evidence ID**: Used evidenceId for document capture in identity workflows
## Compliance Hub
No significant changes this month.
# January 2024
Source: https://docs.zenoo.com/changelog/2024-01
Platform infrastructure improvements and Studio workflow enhancements highlighted this month's releases.
## Platform
* **DynamoDB migration**: Moved string components and sharables from Kafka state store to DynamoDB for improved scalability and reliability
* **Component configuration**: Added support for component config encryption and AWS Secrets Manager integration
* **DSL logging**: Introduced DSLLogger for better debugging and monitoring capabilities
* **Component metadata**: Components can now access their own metadata within definitions
* **AWS credentials**: Switched to default credentials chain with support for assume-role authentication
## Studio
* **API Init interaction**: New interaction type for initializing verification flows with custom payloads
* **URL redirect interaction**: Added ability to redirect users to external URLs during flows
* **User management**: Improved user invitation workflow with automatic list refresh
* **Code editor themes**: Added ability to specify custom themes for code editors
* **Tutorials system**: Enhanced onboarding tutorials with permission controls and continuous guidance
* **Flow upgrading**: Improved migration scripts for safely upgrading legacy flows
## Client SDK
* **Idemia SDK updates**: Pass sessionId as evidenceId, removed deprecated options
* **Input improvements**: Fixed number input return types and date picker blur events
* **Country selection**: Removed rarely used countries similar to US for cleaner selection lists
# February 2024
Source: https://docs.zenoo.com/changelog/2024-02
Component configuration and validation improvements dominated this month's development.
## Platform
* **Component config service**: Introduced centralized component configuration management with validation
* **Component DSL validation**: Added name uniqueness validation for components
* **Execution optimization**: Improved exchange processor performance and reduced memory usage
* **Sharable improvements**: Enhanced sharable token handling with better expiration management
* **AWS DynamoDB**: Enabled async credentials refresh for better reliability
## Studio
* **Component builder with config**: Enhanced component building workflow with configuration support
* **Validation improvements**: Better error messages and validation feedback in flow editor
* **Target execution**: Refactored execute target event handling
* **Libraries build**: Optimized build process to run libraries in parallel
## Client SDK
* **Custom select search**: Fixed search functionality in custom select components
* **Text area improvements**: Added newline support for text areas
* **Acuant SDK**: Updated to version 11.8.0 with improved document capture
# March 2024
Source: https://docs.zenoo.com/changelog/2024-03
Global table management and AWS infrastructure improvements were the focus this month.
## Platform
* **Global DynamoDB tables**: Added automatic creation and management of global tables on startup
* **AWS secrets replication**: Implemented secrets replication across regions for high availability
* **Component config references**: Added support for referencing configuration values between components
* **Config logging**: Enhanced logging for component configuration operations
* **Sharable optimization**: Optimized DynamoDB costs for sharable deletion and expiration
## Studio
* **Workflow builder**: Improved workflow definition and validation
* **Error handling**: Better error messages and recovery in flow execution
* **User experience**: Various UI polish and usability improvements
## Client SDK
* **Acuant improvements**: Camera reinitialization fixes and better loading states
* **Iovation SDK**: Properly clear global window properties on unmount
* **File upload**: Added manual rotation for Acuant file uploads
# April 2024
Source: https://docs.zenoo.com/changelog/2024-04
API authentication and component execution received major updates this month.
## Platform
* **Execute API authentication**: Implemented token-based authentication for component execution
* **Native component config**: Added native component configuration support
* **OAuth authentication**: Added OAuth token authentication for HTTP connector
* **Component config validation**: Enhanced validation at component registration
* **Exposed functions**: Support for executing exposed functions with form-data
* **Config deletion endpoint**: Added API endpoint for deleting component configurations
## Studio
* **Form validation**: Improved form field validation with better regex support
* **User guides**: Updated navigation and documentation
* **Settings improvements**: Better API key management and generation
* **Target creation**: Enhanced new target workflow with clearer steps
## Client SDK
* **Phone input**: Added country code formatting for phone numbers
* **Acuant camera**: Fixed camera frame and back button handling
* **Alert system**: Fixed duplicate alert firing issue
* **Image rotation**: Improvements to image rotation functionality
# May 2024
Source: https://docs.zenoo.com/changelog/2024-05
Major platform upgrades and client SDK enhancements highlighted this month.
## Platform
* **Java 21 & Spring Boot 3.2.5**: Upgraded to latest Java and Spring Boot versions
* **Component timestamps**: Added timestamps to component DynamoDB records
* **Attribute optimizations**: Improved attribute serialization and deserialization performance
* **File cache authentication**: Added authentication support for file cache API
* **Removed attribute export**: Cleaned up deprecated attribute export functionality
## Studio
* **Flow referencing**: Enhanced ability to load URLs with multiple levels of depth
* **Read-only mode**: Added read-only mode for targets in specific scenarios
* **Release controls**: Improved live release button behavior with better validation
* **Documentation download**: Fixed memory leak when downloading documentation
## Client SDK
* **New DateInput component**: Complete redesign of date input with better validation
* **Node.js update**: Upgraded to newer Node.js version with webpack updates
* **Masked input**: Added options for masked input behavior without mask characters
* **Validation skipping**: Added ability to skip validations on form submit when needed
# June 2024
Source: https://docs.zenoo.com/changelog/2024-06
Storage layer improvements and HTTP connector enhancements were completed this month.
## Platform
* **Storage DSL**: Added new storage DSL for managing persistent data
* **JWT authentication**: Added JWT body option to HTTP connector for token-based auth
* **DynamoDB retries**: Improved retry logic with 500ms timeout and automatic retries
* **Component API keys**: Added ability to specify API keys for components in Spring environment
* **MSK IAM Auth**: Updated MSK IAM authentication library
## Studio
* **Async file system**: Implemented async file operations for better performance in builder
* **Smartlook integration**: Added Smartlook user identification for better analytics
* **SDK test management**: Improved test handling with better skip logic
* **Layout fixes**: Safari and mobile device layout improvements
* **Project locking**: Better notifications for project unlock events
## Client SDK
* **Asset validation**: Added checks for asset existence before loading
* **Date handling**: Properly handle dates before minimum unix date
* **Date input validation**: Validate on blur instead of change for better UX
# July 2024
Source: https://docs.zenoo.com/changelog/2024-07
Execution context improvements and sharable functionality enhancements marked July's releases.
## Platform
* **Execution namespace**: Added execution namespace support for better isolation
* **Storage payloads**: Implemented hub-storage as separate module for payload management
* **AWS secret storage**: Added reactive AWS secret storage support
* **Component deletion**: Added delete component endpoint to API
* **DynamoDB retries**: Enhanced retry mechanism for DynamoDB operations
## Studio
* **Static pages**: Added support for static pages in flows
* **Sharable headless functions**: Introduced headless sharable functions for API-only flows
* **Async Hub results**: Support for async result handling from Hub
* **Flow reference tokens**: Better token management for flow reloading
## Client SDK
* **Datepicker ISO format**: Fixed date format handling without ISO format
* **Console feed**: Migrated to fork of console-feed module for better debugging
* **Favicon handling**: Prevent loading favicon.ico for targets in subfolders
* **Resend code loader**: Added loader indicator to resend code button
# August 2024
Source: https://docs.zenoo.com/changelog/2024-08
Sharable token enhancements and DSL extensions were the primary focus this month.
## Platform
* **API key management**: Migrated to DynamoDB for component-to-API-key caching with AWS Secret Manager integration
* **Sharable namespace**: Added sharable namespace setter for better organization
* **Await improvements**: Enhanced await functionality with child namespace support and attribute-based configuration
* **DSL extensions**: Added extension configuration support
* **Custom mappers**: Support for native custom mappers in data processing
* **Component registration**: Non-reactive registration API for better performance
## Studio
* **Form validation**: Improved validation schema compilation before page mount
* **Idemia SDK**: Substituted deprecated methods with environment and connectivity checks
* **GTM integration**: Removed duplicated change page events for Google Tag Manager
* **File naming**: Added ability to change file names in uploads
## Client SDK
* **Acuant SDK**: Migration to Acuant SDK version 11.9.0
* **File upload cleanup**: Properly clean up Acuant file uploads on file change
* **Slider events**: Added onMouseUp event handler to Slider component
* **Checkbox groups**: New checkbox group component for multiple selections
# September 2024
Source: https://docs.zenoo.com/changelog/2024-09
Native component support and HTTP transport improvements headlined this month.
## Platform
* **Native hub components**: Added support for components native to the hub platform
* **Mocking support**: Introduced mocking capabilities for testing
* **Local path references**: Support for local path references in workflows
* **HTTP DSL for workflows**: Added HTTP DSL support in workflow definitions
* **HTTP response handlers**: Implemented command and response handlers for HTTP operations
* **JSON schema validation**: Added JSON schema validation support
* **Exchange error payloads**: Better error payload handling in exchanges
## Studio
* **Workflow DSL validation**: Fixed path cycle detection in workflow validation
* **Component resolution**: Improved revision resolution for native components
* **Test integration**: Better test handling for design studio generated workflows
* **Component logging**: Enhanced component not found logging with WARN level
## Client SDK
* **Sharable validation**: Fixed sharable token validation for static pages
* **Phone formatting**: Added ISO date-only format support
* **Expression refresh**: Automatic refresh of expressions when visited state changes
# October 2024
Source: https://docs.zenoo.com/changelog/2024-10
HTTP client improvements and file handling enhancements dominated October development.
## Platform
* **HTTP file caching**: Store images and files from HTTP responses to file cache
* **Application-level HTTP config**: Added hub-wide HTTP client configuration
* **File cleanup**: Automatic file cleanup on execution termination and expiration
* **Attribute find method**: New method for finding attributes by path
* **Config validation toggle**: Added toggle for enabling/disabling config validation
* **Response body mapping**: Fixed mapper for null response bodies
* **Component timeout**: Better handling of component usage timeouts
## Studio
* **Portal containers**: Allow custom containers for portal rendering
* **Async execution**: Improved async sharable initialization and flow handling
* **Console debugging**: Reverted console-feed fork for better stability
## Client SDK
* **Masked input length**: Fixed length handling on masked input fields
* **YAML context actions**: Exposed validate, submit, and continue actions in YAML context
* **Select list styling**: Fixed small text display in select lists
# November 2024
Source: https://docs.zenoo.com/changelog/2024-11
Transport architecture refactoring and embedding capabilities were introduced this month.
## Platform
* **Configurable HTTP transport**: Made transport layer configurable for HTTP operations
* **Exchange event refactoring**: Separated execution request events for better organization
* **Bearer authentication**: Added bearer token authentication to transport layer
* **Fast-fail URLs**: HTTP requests now fail fast for invalid URLs
* **Event separation**: Split sharable Kafka topics into requests and events for metrics
## Studio
* **iFrame embedding**: Complete embedding support for rendering in iFrames
* **Embed configuration**: Added embed.js with scope and init URL handling for embedded flows
* **Asset packaging**: Improved asset bundling in final packages
## Client SDK
* **Embed authentication**: Fixed cookie handling in embedded contexts
* **Analytics parameters**: Support for analytics URL parameters and start tokens
* **Notification events**: Better recognition and handling of parent page notifications
* **Select styling**: Font size improvements in select lists
# December 2024
Source: https://docs.zenoo.com/changelog/2024-12
Multipart form support and sharable execution improvements concluded the year.
## Platform
* **Multipart form data**: Enhanced support for multipart form data with proper MIME type handling
* **URL encoding control**: Added flag to disable URL encoding when needed
* **Sharable setter results**: Fixed handling of multiple sharable setter results
* **DynamoDB optimization**: Further optimizations for DynamoDB query performance
## Studio
* **React 19 upgrade**: Upgraded to React 19 for improved performance
* **Mobile layout fixes**: Fixed mobile layout rendering issues
* **Embed event handling**: Improved parameter passing to embed event emitter
* **Cookie management**: Better cookie helpers for embedded scenarios
## Client SDK
* **Node.js 22.12.0**: Upgraded to latest Node.js LTS version
* **PDF placeholders**: Added PDF placeholders when preview fails to load
* **File upload errors**: Remove all files on error during upload process
* **Phone validation**: Fixed cases where only country code is in value
# January 2025
Source: https://docs.zenoo.com/changelog/2025-01
Major improvements to the Compliance Hub this month, including a new risk scoring engine, multi-language verification workflows, and automation rule diagnostics.
## Platform
* **Multipart form data support**: The HTTP connector now supports multipart form data requests, enabling file uploads and complex payloads through verification flows
* **URL encoding control**: Added an option to disable automatic URL encoding in HTTP connector requests, giving integrators more control over outbound API calls
* **Execution UUID stability**: Fixed an issue where execution UUIDs could change mid-flow, improving traceability across distributed processing steps
* **File download MIME types**: Improved MIME type handling for file downloads, ensuring correct content-type headers in API responses
* **Component config resolution**: Reworked how component configuration versions are resolved at registration time, fixing edge cases where stale configs were served
* **Sharable processor improvements**: Fixed issues with the SharableProcessor and improved error handling for edge cases in distributed state management
* **Documentation sync**: Migrated platform documentation to the new ReadMe-hosted site with fixed links and consistent navigation
## Studio
* **Centralized deployments**: Deployment jobs have been migrated to a central deployments repository, standardizing the release pipeline across environments
* **Improved session handling**: Unauthenticated users are now redirected properly, and target unlock behavior on logout is more reliable
* **Cognito permissions update**: Updated Amplify and Cognito integration with improved admin query permissions and Node.js version alignment
* **E2E test workflow improvements**: Restructured end-to-end test pipelines with better environment handling, workflow naming, and dependency management
## Client SDK
* **Parent page notifications**: The embedded SDK now properly relays completion and status notifications to the parent page via the embedding event system
* **File upload error recovery**: When a file upload fails, all partially uploaded files are now properly cleaned up instead of leaving partial state
* **PDF preview fix**: Fixed PDF document placeholders that failed to load in certain verification card scenarios
* **Phone input country code**: Resolved a bug where phone inputs could display only the country code without the number
* **Dependency audit**: Updated all dependencies to address known security vulnerabilities
## Compliance Hub
* **Risk score engine**: New risk scoring system that calculates total risk from category-based variables, displays all score components including zero-value items, and loads country values from Alpha-3 code global value sets
* **Multi-language verification emails**: Verification request emails and SMS messages now use the language-specific email template, with language passed through the tokenizer link
* **Automation rule diagnostics**: The system now detects and records why automation rules fail, creating detailed result records that explain which conditions were not met
* **Biometrics UI configuration**: The biometrics section is now configurable through settings, with new positive check values for more granular pass/fail indicators
* **User-friendly error messages**: Replaced cryptic error messages with clear guidance when Hub settings are missing, Remote Site settings are not configured, or unexpected data formats are received
* **Verification link tokenization**: Improved the SMS verification link flow to validate tokenizer configuration before sending, preventing broken links
* **Risk score labels**: The risk score component now displays user-friendly labels instead of API names for single-select and multi-select variables
* **Requirement cloning**: Added the ability to clone existing verification requirements along with their associated documents, streamlining setup for similar verification types
# February 2025
Source: https://docs.zenoo.com/changelog/2025-02
This month introduced a shared execution state system in the platform, while Compliance Hub received a major automation rule simulation feature and expanded document type management.
## Platform
* **Shared execution state**: Components can now share state across execution boundaries using a new shared attribute namespace, enabling complex multi-step flows where one component needs data from another
* **Component factory fix**: Resolved an issue where the component factory could produce incorrectly initialized instances, causing intermittent failures in production flows
* **Config deserialization fix**: Fixed a deserialization bug that caused component configurations with certain key patterns to fail on load
* **Duplicate key handling**: Resolved an issue where duplicate configuration keys could silently overwrite values during component registration
* **Config version logging**: Added detailed logging for configuration version resolution, making it easier to diagnose version mismatch issues in production
* **Exchange processor MDC logging**: Added MDC (Mapped Diagnostic Context) to exchange processors, improving log traceability across distributed processing pipelines
## Studio
* **Nexus provider configs**: Added configuration support for the Nexus verification provider
* **Base64 webhook support**: Added support for base64-encoded webhook payloads, enabling binary data in webhook-driven flows
* **User attribute caching optimization**: Reduced the number of Cognito API requests by optimizing how user attributes are cached, improving page load times
* **Session timeout alignment**: The flow session timeout now matches the start token TTL, preventing premature session expirations
* **Mockup video support for testing**: Cypress end-to-end tests now support mockup video files, enabling automated testing of video capture workflows
* **Target metadata fix**: Fixed an issue where target name and ID were incorrectly populated in Hub metadata
* **Cognito function update**: Updated the Cognito authentication function with improved permissions handling
## Client SDK
* **Embed authentication overhaul**: Comprehensive fix for authentication in embedded (iframe) flows, resolving cookie handling, event emission, and scope issues that affected cross-origin sessions
* **Analytics URL parameters**: Embedded flows now correctly pass URL parameters and start tokens to the analytics provider
* **Cookie management utilities**: New helper functions for reading and writing cookies in embedded contexts, supporting the authentication overhaul
* **Notification event handling**: Improved detection and forwarding of notification events from the embedded SDK to the parent page
* **Mobile layout fix**: Corrected layout issues on mobile devices that caused content to overflow or misalign
* **Headless sharable fix**: Fixed an issue where headless sharable initialization could incorrectly persist stale results
## Compliance Hub
* **Automation rule simulation results**: Administrators can now view simulation results for individual verified entities and see exactly which records would pass or fail each rule
* **Verification document subtypes**: Added a new dependent picklist for document subtypes that dynamically updates based on the selected document type, along with expanded type and subtype values
* **Verification document grouping**: Verification documents are now grouped by type when displayed in the Hub integration, providing a cleaner overview
* **REST endpoint for field mapping values**: New API endpoint returns mapped field values for a given record, enabling external systems to retrieve processed verification data
* **Multi-language verification links**: Tokenizer and verification links now include the selected language parameter, and a language identifier was added to the field mapping dropdown
* **Risk score update fix**: Country-based risk score points now update immediately when the risk score variable changes, without requiring an additional save
* **WorldCheck empty result fix**: Fixed an issue where WorldCheck responses with empty result arrays caused the screening component to display incorrectly
* **Automation rule provider filtering**: Automation rules now display the provider field on the page layout and filter correctly to show relevant rules
* **Event logging for status changes**: A new event log record is created when a ContentVersion status is cleared, maintaining a complete audit trail
* **User Interaction field**: Added the User Interaction field to the Verification Document object and REST response, supporting conditional review workflows
* **Verification prerequisite check**: The system now validates that verification documents are properly defined before allowing a new verification to be created
# March 2025
Source: https://docs.zenoo.com/changelog/2025-03
The platform gained a callback system and component tester framework this month, while Compliance Hub received major improvements to document handling, entity management, and field mapping.
## Platform
* **Callback support**: Flows can now register callbacks that execute when specific conditions are met, enabling asynchronous patterns like waiting for external webhook confirmations before proceeding
* **Component tester framework**: New testing framework for building and running integration tests against individual components, making it easier to validate component behavior in isolation
* **HTTP JSON response matching**: The HTTP connector can now match and route based on JSON response content, enabling conditional branching based on API response payloads
* **Attribute find fix**: Resolved ambiguity in the attribute find method that could return unexpected results when multiple attributes shared similar names
* **Component closure caching**: Optimized how component closures are cached in memory, reducing processing overhead for frequently invoked components
* **Null attribute removal**: New `removeNulls()` method on attributes strips null values before forwarding data, simplifying downstream processing logic
* **Component revision optimization**: Optimized the component revision resolver to reduce resolution time for deployments with many component versions
## Studio
* **Analytics provider save/load fix**: Fixed an issue where analytics provider configurations were not correctly saved or retrieved from target settings
* **Loader component fix**: Resolved a rendering issue with the loader component in the template that could cause flickering during page transitions
* **Translation corrections**: Fixed mistranslated UI strings that affected the localized experience in several languages
* **Onboarding redesign**: Rebuilt the onboarding flow with a refreshed visual design and updated content
* **Target preview accuracy**: Preview rendering now uses the resolved configuration, ensuring what you see in the Studio matches the deployed experience
* **Test assertion messages**: End-to-end test failures now include human-readable assertion messages, making test debugging significantly faster
* **Library credential module**: New module for obtaining component credentials through library backend functions, enabling secure credential exchange for third-party integrations
* **Target name via settings**: Target names can now be changed through the settings data API instead of requiring a separate update call
## Client SDK
* **Deprecation warning fixes**: Resolved React deprecation warnings that appeared in the browser console during verification flows
* **Store2 security update**: Updated the store2 dependency to address a security vulnerability in local storage handling
## Compliance Hub
* **Document name display**: Verification document components now display the actual document name in the header instead of the generic "Other" label
* **Auto-create Account for Contacts**: When a Contact is created or updated without a related Account, the system now automatically creates one, preventing orphaned contact records
* **Source of Wealth/Funds mapping**: Added field mapping and UI display for "Source of Wealth" and "Source of Funds" data, surfacing important compliance information from the "Other" document type
* **Multiple "Other" document support**: The verification component now correctly processes and displays multiple documents of the "Other" type within a single verification
* **Lookup field email/mobile mapping**: Fixed an issue where email and mobile fields from lookup object mappings could not be used in verification request workflows
* **Verification data retry parameter**: Added a configurable "Maximum Verification Data Retries" parameter that is passed to the tokenizer request, controlling how many times the system retries data retrieval
* **Verification prerequisite check**: The system now validates that verification documents are properly defined before allowing a new verification to be created
* **Field mapping type conversion fix**: Resolved an "Invalid conversion from runtime type Id to Map" error that occurred in specific field mapping configurations
# April 2025
Source: https://docs.zenoo.com/changelog/2025-04
Studio received a major update with target labeling, advanced filtering, and API-only target support, while the platform improved handler usage tracking and removed legacy connector metrics.
## Platform
* **Handler usage tracking fix**: Corrected an issue where handler usage metrics were not properly tracked, ensuring accurate reporting of component utilization
* **Connector meter removal**: Removed legacy connector metrics in favor of the newer observability system, reducing overhead in the processing pipeline
## Studio
* **Target labels**: Projects can now be tagged with custom labels for organization and quick filtering, making it easier to manage large numbers of verification flows
* **Advanced target filtering**: New filter panel on the targets list lets users filter by label, status, and other criteria, replacing the previous simple search
* **API-only targets**: Full support for API-only verification targets that operate without a UI flow, enabling headless verification scenarios where the client application manages the user experience
* **API-only library upgrades**: API-only targets can now have their underlying libraries upgraded independently, keeping integrations current without affecting other flows
* **Hub Studio direct calls**: New Studio function enables direct communication with the Hub backend, supporting advanced library and template operations
* **Smartsearch automated test**: Added automated smoke test for the Smartsearch integration to catch regressions early
* **Trinsic smoke test**: New automated test for the Trinsic identity verification library
* **Search label fix**: Fixed a bug where the search label in the target list did not update correctly after clearing a search
## Compliance Hub
* **Verification requirement cloning**: Users can now clone verification requirements along with their selected documents, dramatically reducing setup time for similar verification scenarios
* **Document type-subtype endpoint**: New REST endpoint returns all valid verification document type and subtype combinations, enabling external systems to offer guided document selection
* **Address image handling**: Disabled image display in Address (Worldview) verifications and added proper handling for 1AU status messages
* **EKYC tokenizer parameter**: Added a new EKYC parameter to the tokenizer payload, supporting enhanced electronic Know Your Customer verification flows
* **Active field mapping filter**: Field mapping now correctly filters to show only active fields, preventing accidentally mapped inactive fields from appearing in configurations
* **Other Data display**: Added a check for the presence of Other\_Data values before attempting to display the component, preventing empty section rendering
# May 2025
Source: https://docs.zenoo.com/changelog/2025-05
A landmark month with React 19 upgrades across both Studio and the Client SDK, a new plugin system for the platform, and generic verification reporting in Compliance Hub.
## Platform
* **Plugin system**: Introduced an embedded plugin architecture using Apache Karaf, enabling dynamic loading and versioning of platform connectors without redeploying the core engine
* **Connector versioning**: Connectors can now be versioned independently, allowing gradual rollouts and rollbacks of individual integration components
* **Component revision tracking**: Component revisions are now dynamically updated on registration, ensuring the latest version is always served
* **Attribute collect method**: New method to gather attributes from multiple sources into a single collection, simplifying data aggregation patterns in flows
* **API key lifecycle management**: Old API keys are now automatically removed when exposed component keys are updated, preventing stale credentials from accumulating
* **Component tester fix**: Resolved an issue where the component tester failed to initialize certain component types correctly
* **Spring Boot 3.3.11**: Updated the underlying Spring Boot framework to address security vulnerabilities and improve stability
## Studio
* **React 19 upgrade**: Migrated the entire Studio application to React 19, bringing improved performance, concurrent rendering support, and access to the latest React ecosystem
* **AI chat assistant**: New AI-powered chat interface in the target builder helps users configure verification flows through natural language instructions
* **AI assistant permissions**: Added a UI-level permission to control which users can access the AI chat assistant
* **Pinecone vector database integration**: Connected the AI assistant to Pinecone for semantic search across documentation and flow configurations
* **Read-only Docker image**: The Docker container now runs with a read-only root filesystem for enhanced security compliance
* **Database v2 connection**: Migrated to the v2 database connection layer for improved reliability and performance
* **Tutorials fix**: Replaced deprecated `setImmediate` calls with `queueMicrotask` in tutorials, fixing a compatibility issue in modern Node.js environments
* **Invalid workflow handling**: Invalid workflows no longer block target generation, allowing partially configured projects to still build successfully
* **Target list filters via cookie**: Filter selections on the targets list page now persist across page navigations using cookies
## Client SDK
* **React 19 upgrade**: The embeddable verification SDK has been upgraded to React 19, aligning with the Studio upgrade and bringing performance improvements to end-user verification flows
* **React DOM client exposure**: ReactDomClient is now available on the window object for applications that need direct React DOM access in custom integrations
* **React global scope**: React is now exposed in the global scope, enabling easier integration with external scripts and analytics libraries
## Compliance Hub
* **Generic verification report**: A new report template is now available for all verification types, providing a standardized PDF export regardless of document category
* **Template language refresh**: Email and document templates now automatically refresh when the user changes the language, ensuring the correct localized version is always presented
* **Auto-open pending verification**: New setting automatically opens the first pending verification when entering a case, saving reviewers a click in their most common workflow
* **Mandatory approval/decline comments**: Comments can now be made mandatory when approving or declining verification documents, ensuring compliance teams always document their reasoning
* **Automation rule execution fix**: Fixed a bug where automation rules were silently failing to execute under certain conditions
* **User Interaction Required**: Updated the verification endpoint and trigger to use the new `User_Interaction_Required` field, enabling flows that conditionally require manual review
* **Report formatting improvements**: Updated report layout with improved page-break handling for multi-page documents
# June 2025
Source: https://docs.zenoo.com/changelog/2025-06
The platform added base64 file support for the execute API, while Studio integrated Mixpanel analytics with error tracking and Compliance Hub enhanced audit logging with biometrics results.
## Platform
* **Base64 file support**: The execute API now accepts base64-encoded files directly in requests, simplifying integrations that need to pass document images without a separate upload step
* **Connector documentation overhaul**: Expanded HTTP connector docs with detailed sections on query parameters, headers, authentication, and sharable patterns
* **LocalRun plugin removal**: Removed the deprecated localRun plugin, directing developers to the Docker-based local development setup instead
## Studio
* **Mixpanel analytics integration**: Integrated Mixpanel analytics with JavaScript error tracking, configurable initialization options, and provider settings driven by JSON schema
* **GraphQL upload migration**: Migrated from the deprecated graphql-upload library to graphql-upload-minimal, resolving compatibility issues with newer Node.js versions
* **Accessibility preparation**: Started accessibility improvements in the target builder, laying groundwork for WCAG compliance in future releases
* **S3 deployment security**: Updated the S3 sync command in the deployment strategy, removing public ACL options for improved security
* **Read-only filesystem hardening**: Refined the Docker configuration to fully support read-only root filesystem mode in production environments
* **Cypress test log levels**: Added configurable log levels to Cypress tests, reducing noise in automated test output
## Client SDK
* **Mixpanel error tracking**: The SDK now captures and reports JavaScript exceptions to Mixpanel, enabling proactive monitoring of client-side errors in production verification flows
* **Mixpanel initialization options**: Added configurable initialization options for Mixpanel, including async loading support for improved page load performance
* **React DOM client fix**: Resolved an issue where the React DOM client reference was not correctly assigned, fixing initialization failures in certain embedding configurations
## Compliance Hub
* **Biometrics audit logging**: Biometrics verification results are now included in the audit log, providing a complete trail of face comparison and liveness check outcomes
* **Error list in subsection status**: Verification subsections now display individual error messages, making it easier to identify exactly which checks failed within a document review
* **Evidences section**: New "Evidences" section displays uploaded images directly within the verification review, giving reviewers immediate visual access to submitted documents
* **Event notification default recipients**: Added a default value for event notification recipients, ensuring critical verification events are always routed even when custom recipients are not configured
* **Optional address in reports**: Addresses are now treated as optional in verification reports, preventing empty address blocks from appearing when no address data was collected
* **Document list empty state**: Fixed an issue where verification components would error when the document list was empty instead of showing a graceful empty state
* **Null JSON handling**: Resolved a null pointer error when processing verification records with missing JSON data
# July 2025
Source: https://docs.zenoo.com/changelog/2025-07
The platform introduced configurable memory buffer limits and an improved component testing framework, while Studio overhauled permissions storage and Compliance Hub added WorldCheck ongoing monitoring.
## Platform
* **Configurable memory buffer limits**: Added a `max-in-memory` buffer size setting for WebFlux, preventing out-of-memory issues when processing large file uploads or API responses
* **Component tester improvements**: The component tester now uses the Component Factory pattern, providing more realistic test execution and better parity with production behavior
* **Component tester documentation**: Published the component tester documentation on the platform docs site, making it accessible to all integration partners
* **Transport base URL support**: Fixed base URL configuration support in the HTTP transport layer, enabling components to use relative paths with a configured base URL
## Studio
* **User permissions compression**: Overhauled how user permissions and library access are stored, compressing the `availableLibraries` field and migrating to a more efficient storage format for better scalability
* **Target cache cleanup**: Each target now has its own cache folder that is cleaned up independently, preventing stale data from one project from affecting another
* **Encrypted test environments**: Cypress test environment variables are now encrypted, preventing accidental exposure of API keys and credentials in CI/CD logs
* **File support for API-only targets**: API-only verification targets can now accept and process file uploads, expanding headless verification capabilities to document-based flows
* **Validator result clearing**: Added a button to clear validator results from the top bar, making it easier to reset validation state during flow development
* **Code suggestion fix**: Resolved a bug in the code suggestion feature that could recommend incorrect completions in certain contexts
## Client SDK
* **Accessibility features**: Added accessibility improvements including proper ARIA attributes, keyboard navigation support, and screen reader compatibility for verification flow components
* **Google Maps API key handling**: Fixed handling of falsy API key values in the Google Maps integration, preventing initialization errors when the key is not yet loaded
* **Mixpanel error capture**: Console errors are now captured and forwarded to Mixpanel for centralized error monitoring across deployed verification flows
## Compliance Hub
* **WorldCheck ongoing monitoring**: Added a daily scheduled job that checks for updates in WorldCheck screening results, with case system ID tracking for continuous monitoring of screened entities
* **Case rating endpoint**: New endpoint and modal for managing case ratings, enabling supervisors to rate and review the quality of completed case assessments
* **Risk and reason in case results**: The case result summary box now displays both the risk level and the reason for the decision, giving reviewers immediate context
* **Report data enrichment**: Reports now include all content from the Other\_Data field, ensuring no verification information is lost in PDF exports
* **Worldview split API calls**: When user interaction is not required, the system now makes separate API calls for address and identity verification through Worldview, improving data completeness
* **Provider subtype corrections**: Fixed the "Qualid" provider subtype display to show the correct "Qual-ID" branding
* **Document part consistency**: Document groups now always include individual parts even when only one document is present, ensuring consistent rendering in the review interface
* **Country type normalization**: Standardized the country field format in API responses to use consistent type codes across all endpoints
* **Date handling fix**: Resolved an "Invalid Date" error that could occur in upsert record responses when date fields contained edge-case formats
* **Verification action keyboard fix**: Resolved an issue where pressing space or enter on a verification action button would clear the status instead of triggering the expected action
# August 2025
Source: https://docs.zenoo.com/changelog/2025-08
WorldCheck ongoing monitoring and case rating workflows were introduced in the Compliance Hub this month.
## Platform
* **HTTP multipart support**: Added multipart form data handling with proper MIME type detection, enabling file upload workflows through the HTTP connector
## Studio
* **Password fields in schema forms**: JSON schema forms now support password-type input fields, allowing secure credential collection in connector configurations
* **Validator result clearing**: Added a button to the top navigation bar to quickly clear validation results, improving the flow builder feedback loop
## Client SDK
* **Google Maps API key handling**: Fixed an issue where falsy API key values were not properly handled, preventing map components from initializing correctly
## Compliance Hub
* **WorldCheck ongoing monitoring**: A daily batch job now automatically checks for updates in WorldCheck, with case system ID tracking to maintain screening continuity
* **Case rating endpoint and modal**: Added a dedicated API endpoint and reviewer-facing modal for assigning case risk ratings during the disposition workflow
* **Risk and reason in case results**: Case result boxes now display the assigned risk level and reason values, giving reviewers immediate visibility into disposition outcomes
* **Verification link-only option**: Verification requests can now generate a standalone link without triggering an email or SMS, providing more flexibility for client communication
* **Duplicate verification prevention**: The system now checks for existing active verifications before creating new ones, preventing redundant verification requests
# September 2025
Source: https://docs.zenoo.com/changelog/2025-09
A new Autocomplete input component and enhanced status handling were the highlights of September.
## Platform
* **Spring Boot 3.5.6 upgrade**: Upgraded the core framework to Spring Boot 3.5.6, including fixes for parallel test execution stability
* **Transport validation**: Implemented DSL-level validation for HTTP transport configurations, catching misconfigurations before runtime
* **Enhanced onStatus handling**: The `onStatus` handler now supports matching against multiple status codes and ranges in a single declaration, simplifying error handling logic
* **Component validation testing framework**: Added a comprehensive testing framework for component validators, including new assertion methods for verifying component behavior
## Studio
* **Redesigned onboarding experience**: Rebuilt the onboarding flow with updated styling, improved footer text colors, and a cleaner visual layout
* **Target lock management**: Administrators can now force-unlock specific targets and clear lock files, with the confirm button properly disabled when no folders are selected
* **Schema description output**: Schema description output now includes the field path, making it easier to locate fields in deeply nested configurations
## Client SDK
* **Autocomplete input component**: Introduced a new Autocomplete input with support for complex values, loading indicators, and improved suggestion rendering
* **Accordion styling update**: Refreshed the visual styling of the Accordion component to align with the latest design system
* **Nested field validation**: Fixed handling of the `visited` flag and error display for nested fields, ensuring validation feedback appears correctly in complex forms
## Compliance Hub
* **Risk assessment scoring overhaul**: Updated the risk assessment component with a new scoring model and verification status integration, providing more granular risk evaluation
* **Document approval confirmation**: A confirmation modal now appears when approving or rejecting documents, requiring reviewers to acknowledge their action before it takes effect
* **BCC email setting**: Added a configurable setting to make the BCC address mandatory when sending verification request emails
* **OCR data visibility toggle**: A new setting controls whether the "Data on document OCR" section is displayed, allowing organizations to hide raw OCR output from reviewers
* **Address and country improvements**: Collected addresses now display full country names, with proper spacing and formatting corrections throughout the identity section
* **Verification link confirmation modal**: Selecting the "Verification Link Only" option now triggers a confirmation modal with a fallback copy function for clipboard support
* **Audit log BCC and refresh**: Added BCC option support in the customize email modal, and relocated the WorldCheck refresh button for better discoverability
# October 2025
Source: https://docs.zenoo.com/changelog/2025-10
Frontend analytics events and a new Hub analytics provider bring end-to-end session tracking to verification flows.
## Platform
* **Frontend analytics events**: Added support for capturing analytics events from the client SDK, with timestamp and UUID generation moved server-side for consistency
* **Docker image caching**: CI/CD pipelines now cache Docker image layers, significantly reducing build times for platform deployments
## Studio
* **Execution analytics reports**: A new analytics section displays execution reports with design improvements, giving operators visibility into verification flow performance
* **Hub analytics integration**: The Hub analytics provider is now enabled in templates by default, automatically tracking verification sessions
* **Field mapping enhancements**: Added gender type support and a new field mapping type to the documentation component editor
* **Target lock protection**: Administrators can no longer accidentally remove target locks from admin settings, preventing unintended concurrent editing
## Client SDK
* **Hub analytics provider**: Introduced a new analytics provider that reports verification session data to the Hub backend, including automatic JavaScript error tracking
## Compliance Hub
* **WorldCheck automation rules**: WorldCheck screening results can now trigger automation rules, enabling automatic disposition of matching records based on configurable criteria
* **Audit log enhancements**: Audit logs now include additional fields and support filtering by verification ID, making compliance reviews more targeted and efficient
* **WorldCheck results-only mode**: A new "show only results" setting on the WorldCheck component hides empty screening states, reducing visual noise for reviewers
* **Entity search endpoint**: Added a new REST endpoint for searching entities, enabling external systems to locate records within the Compliance Hub
* **Sanction card reliability**: Fixed an issue where the sanction card displayed "Passed" status even when screening data failed to load
* **Case rating sync**: The case rating picklist now automatically updates when the underlying case rating value changes, keeping the UI in sync with the data model
* **Detail page auto-refresh**: Verification detail pages now automatically refresh when the underlying record is updated, eliminating the need to manually reload
# November 2025
Source: https://docs.zenoo.com/changelog/2025-11
HTTP transport headers and execution analytics reports bring better control and visibility to verification flows.
## Platform
* **HTTP transport headers**: Added support for custom headers in the HTTP transport layer, with corresponding query parameter and header configuration sections in the connector documentation
* **Component SDK dependency management**: The Spring Boot BOM is now exported from the component SDK, ensuring transitive dependencies resolve correctly for downstream consumers
* **Plugin system refactoring**: Refactored the test API and added plugin transport customizers, with comprehensive test coverage and JavaDoc for the WebClient factory
## Studio
* **Execution analytics reports**: A new execution reports view provides aggregated analytics data for verification flows, with design improvements for clarity
* **Credential management fixes**: Fixed an issue where unsaved credential changes were lost when switching environments, and resolved the upgrade modal appearing incorrectly for API-only targets
* **Library update detection**: The system now checks whether target libraries need updating and notifies users, preventing deployments with outdated dependencies
* **Sidebar icon refresh**: Updated sidebar icons to the latest design system for a more consistent navigation experience
## Client SDK
* **Alphacode matching fix**: Corrected an issue where composite ID strings were incorrectly matched instead of alpha codes, preventing certain country selections from resolving properly
## Compliance Hub
* **WorldCheck identity table update**: Refreshed the worldview table layout in the Identity section for clearer presentation of screening results
* **Sanctions list label clarity**: Updated labels on the sanctions list component to use more descriptive text, reducing ambiguity for compliance reviewers
* **Verification card description fix**: Corrected the matching results description text on verification cards to accurately reflect the screening outcome
* **Content version trigger fix**: Resolved a bug in the content version trigger that could cause errors during document processing
# December 2025
Source: https://docs.zenoo.com/changelog/2025-12
Multi-cloud support and mobile hand-off capabilities were the focus of December.
## Platform
* **Multi-cloud provider abstraction**: Introduced a cloud provider abstraction layer that decouples platform services from AWS-specific implementations, laying the groundwork for multi-cloud deployments
* **Mobile hand-off pattern**: Added a mobile hand-off pattern that propagates terminal route results across device transitions, enabling verification flows that start on desktop and continue on mobile
* **Attribute validation fix**: Fixed attribute validation in handlers to correctly process edge cases in verification flow inputs
## Studio
* **Mobile hand-off support**: The flow builder now supports mobile hand-off interactions, allowing designers to create verification flows that seamlessly transition between desktop and mobile devices
* **Cognito email configuration update**: Updated the Cognito email configuration and invitation template to improve deliverability and branding of user invitation emails
* **Flex-shrink layout property**: Added a `flex-shrink` CSS property option to layout components, providing finer control over how elements resize in responsive layouts
## Client SDK
* **Email input type fix**: Fixed a validation issue with email input fields that could prevent valid email addresses from being accepted
# January 2026
Source: https://docs.zenoo.com/changelog/2026-01
Google Cloud Platform support reached production readiness, and the Compliance Hub launched AML Case Management 2.0.
## Platform
* **Google Cloud Platform provider**: Shipped a complete GCP cloud provider implementation with Pub/Sub messaging, Cloud Storage, and Secret Manager integration, alongside comprehensive contract tests
* **Redis cache reliability**: Fixed an infinite retry loop and timeout issues in the Redis file cache that could cause verification flows to hang under high load
* **AWS SDK version management**: Refactored AWS SDK dependency management and restored the missing MSK IAM authentication dependency, improving build reliability
## Studio
* **US region re-enabled**: Restored the production US region for Studio deployments, expanding geographic availability
* **Library-scoped user invitations**: Users can now be invited with access restricted to specific target libraries, enabling finer-grained access control for multi-team organizations
* **Config override array handling**: Fixed an issue where configuration overrides incorrectly appended items to arrays instead of replacing them, which caused unexpected behavior in nested configurations
* **Upgrade template notification spacing**: Increased the margin on the upgrade template notification to prevent it from overlapping other UI elements
## Client SDK
* **Page direction in YAML context**: Exposed the page direction (LTR/RTL) in the YAML context, enabling verification flows to properly adapt their layout for right-to-left languages
## Compliance Hub
* **AML Case Management 2.0**: Launched the next-generation AML case management system with a Google Apps-inspired design, featuring an issue-first UX, collaborative review, and progressive disclosure of compliance complexity
* **Verification requirement cloning**: Requirements can now be cloned with their associated documents, and new documents can be added during the clone operation, streamlining repetitive setup tasks
* **Custom verification creation**: Simplified the process for creating custom verification documents, with a dedicated description field and improved routing after save
* **Other document type visual update**: Modified the visual component for "Other" document types to display document names in headers instead of generic "Other" text
* **Verification requirement error handling**: Improved error handling during verification requirement modification to display user-friendly messages instead of technical errors
# February 2026
Source: https://docs.zenoo.com/changelog/2026-02
The Compliance Hub AML package received a major update with risk model improvements, a visual layout builder, and a full documentation overhaul.
## Platform
* **GCP documentation and Kafka fixes**: Published comprehensive GCP cloud provider documentation and resolved Kafka Streams configuration issues affecting stream processing stability
* **File cache architecture guide**: Added a detailed architecture guide for the file cache system, covering retry behavior, eviction policies, and operational best practices
## Studio
* **Translation import/export**: Translations can now be imported and exported as files, making it easier to manage localization across environments and collaborate with external translators
* **AI styling assistant**: Added downloadable AI styling assistant files to the Studio container, enabling AI-powered design suggestions during flow building
* **Hub analytics authentication**: Added basic authentication to the Hub analytics endpoint, securing execution report data from unauthorized access
* **User reinvitation fix**: Fixed an issue where reinviting a user to the platform would fail silently, ensuring invitation emails are sent correctly
## Client SDK
* **Include tag array support**: The `!include` YAML tag now supports arrays with fallback values, enabling more flexible configuration composition when referenced files may not exist
* **URL query parameter persistence**: URL query parameters are now preserved in the store across page transitions, preventing loss of tracking parameters and deep-link context during verification flows
* **Design Studio property calculation**: Fixed property calculation for include directives specified as arrays, ensuring correct value resolution in templates
## Compliance Hub
* **Entity-type risk models**: Risk model selection is now entity-type aware, automatically applying the correct model (Individual or Company) based on the case entity, replacing the previous one-size-fits-all approach
* **Visual layout builder**: A new drag-and-drop layout builder lets administrators map vendor data fields to alert display zones with a live preview, eliminating the need for manual field mapping configuration
* **Documentation overhaul**: Published 46 new documentation files including user guides, a unified OpenAPI spec, and a three-tab Mintlify documentation site covering guides, API docs, and API reference
* **API docs migration to Mintlify**: Migrated the full API documentation from ReadMe to Mintlify with validation, structured navigation, and integrated code samples
* **Screening API enrichment**: Enhanced the screening data pipeline with classification-aware routing, country deduplication, and date extraction from WorldCheck responses
* **Vendor mapping AI Magic**: The AI-powered field mapping tool now supports incremental processing, file upload for large payloads, and alert display context with role/zone/alert-type metadata
* **External API resilience**: Introduced error classification (transient, permanent, rate-limited), a circuit breaker pattern, and a retry scheduler for external API calls, with a failure recovery dashboard
* **Cross-case check reuse**: Verification checks can now be reused across cases using a "verify once, reference many" pattern, reducing redundant API calls and speeding up repeat verifications
# April 2026
Source: https://docs.zenoo.com/changelog/2026-04
Studio shipped a redesigned flow editor, smarter branding extraction, bulk translation import/export, and a set of new flow-control building blocks including checkpoints, single-output routing, and an AI styling assistant.
## Studio
* **Flow editor redesign**: A complete redesign of the flow editor layout and logic. Interactions are now displayed as circle nodes for a cleaner, more compact visual representation, and device preview is available directly in the flow editor
* **Branding extraction improvements**: Design Studio now pulls colors, fonts, and visual identity from a client's brand URL more accurately, giving projects a polished look from the start with less manual styling
* **Import/export translations**: Export and import translation files for targets, making it easy to adjust translations in bulk — including using AI tools to translate, proofread, or rewrite content across all languages at once. The translations UI is improved with scrollable language chips and better page name display. Coming next: AI-powered translation editing directly inside Design Studio
* **Reusable start token**: Start tokens can now be reused with a configurable TTL, managed through a new TTL slider UI
* **Checkpoints**: Pages can now be marked as checkpoints. When enabled, the end user cannot navigate back past that page — useful for steps like identity verification or payment confirmation where going back should not be allowed
* **Single output mode & router node**: A new optional migration to single-output mode simplifies flow routing. To support conditional branching in this mode, a new Router node evaluates conditions on interaction outputs and routes the flow to the appropriate next step, replacing the need for multiple output connectors
* **Conditional system error output**: Interactions now support a conditional system error output. By default it leads to a generic error page, providing a consistent fallback for unexpected failures without requiring manual error handling setup
* **AI styling assistant**: Generate custom CSS styles for your project using AI. Export your project's styling reference files, combine them with a prompt, and receive ready-to-paste custom CSS that matches your design requirements. Coming next: AI-powered styling chat directly inside Design Studio
* **Flow editor performance**: Significantly improved rendering speed for large targets, and fixed memory issues and call stack overflow on complex workflows
* **Target changes status**: Improved change detection with a clear display of added and removed nodes in workflow diffs
* **Default sort by last update**: The targets list is now sorted by most recently updated
* **Interaction categorization**: Interactions are organized into categories aligned with ZDM types (IDV, IDP, Biometrics, etc.) for easier discovery
* **Improved library search**: Better keyword matching when searching interactions in the library browser
* **Layout settings override**: Library interactions can now override layout settings per interaction
# May 2026
Source: https://docs.zenoo.com/changelog/2026-05
Studio added target themes, two new analytics providers, sub-flow grouping in the Flow Editor, and a new textarea page-item, alongside hardened target cloning.
## Studio
* **Target themes**: Targets now support themes alongside device settings. Choose a colour scheme and visual style for a target from a single, unified configuration step in the new-target wizard, with theme tokens flowing through to library page-items so previews reflect the chosen look from the very first frame
* **PostHog & Microsoft Clarity in target analytics**: Two new analytics providers join the per-target Analytics → Providers picker. PostHog accepts an API key and an optional custom host; Microsoft Clarity accepts a project ID. Existing provider icons (Hotjar, Mixpanel, Smartlook) were refreshed to render cleanly on both light and dark themes, and PostHog ships with theme-aware icons that swap automatically
* **Sub-flow visual grouping in Flow Editor**: The Flow Editor now visually groups related steps into sub-flows, making complex journeys far easier to read at a glance. Mobile-handoff interactions are pre-declared as a sub-flow group, so the handoff hop renders as a clearly demarcated section rather than a chain of loose nodes
* **Textarea page-item**: Library authors and target designers can now use a dedicated textarea field type for multi-line free-text input, complementing the single-line text input. The template form and heading margins around new field types were also polished
* **Cloning hardening**: Cloning a target now regenerates tokens, resets analytics state, and treats credential copying as an explicit choice gated by a new permission. The clone modal layout and credentials section were also polished
* **Paragraph editor fix**: Opening an existing Paragraph item now correctly seeds the editor with its saved markdown again, fixing a regression that caused the editor to open empty
# July 2026
Source: https://docs.zenoo.com/changelog/2026-07
Studio gained an in-editor AI assistant, AI-powered translations, and early credential checks, alongside a smoother publishing flow and a batch of workflow improvements.
## Studio
* **Studio AI Assistant**: A new AI assistant is now available inside the studio, starting on the flow editor. Ask questions about your flow and get help building it, with answers grounded in exactly what's available in your project. It's the first of several assistants coming to speed up your work
* **AI-powered translations**: Use "Translate all" to fill in every missing translation for a language in one click, with your formatting and placeholders preserved intact. A new translations assistant lets you refine your original English content through chat, and every change is shown for review before it's saved
* **Credentials check**: The studio now warns you early when a project's saved credentials look incomplete or incorrect. A clear warning appears next to Credentials and at the exact place that needs attention, with the fields to fix highlighted the moment you open them. Nothing is blocked — it's a helpful heads-up before your app goes live
* **A better finish for API projects**: When you create an API project, the final step now shows what you actually need to start using it — your connection details and how to call it — instead of website-focused messaging that didn't apply
* **Publish without interruptions**: Publishing no longer stops to ask you to describe your changes. The studio now writes that summary for you automatically, so you can publish in one smooth step
* **Find projects by web address**: Paste a project's live URL into the Projects search to jump straight to it. Searching by parts of the address works too
* **Live project count**: The Projects header now shows a live count that updates as you search and filter (for example, "Projects · 123")
* **Insert variables into paragraphs**: Click a variable to drop it right where your cursor is inside paragraph text, just like it already works for other fields
* **Cleaner settings for API projects**: An empty settings heading no longer appears where there's nothing to configure
* **Smoother sign-out**: Logging out now feels instant instead of lingering on your last screen
# August 2026
Source: https://docs.zenoo.com/changelog/2026-08
A major release: Studio can now be driven by an AI assistant through a new Model Context Protocol (MCP) server, API projects gained live interactive documentation, and the Projects list got a powerful advanced filter — alongside new page elements, richer tutorials, and a wave of reliability work.
## Studio
* **Build projects with AI (beta)**: Studio now exposes a Model Context Protocol (MCP) server, so you can drive it from an AI assistant. Search and clone projects, create one from a template or from a written specification, apply full branding and custom CSS, manage translations, and save and deploy to a sandbox — all through natural-language tools, secured with your Studio login
* **Live API documentation**: API projects now ship interactive, always-up-to-date API documentation, with downloadable OpenAPI and Postman specifications, so integrators can see exactly how to call your flow
* **Advanced Projects filter**: A redesigned filter lets you narrow the Projects list by several criteria at once, with a clear active-count on the filter button so you always know what's applied
* **New page elements**: Toggle, Slider, QR code and Signature are now available as elements you can drop straight into a page
* **Friendlier user-group names**: User groups now display a human-readable name instead of a raw system identifier
* **Richer tutorials**: The in-studio user guides and tutorial pop-ups have been restructured and polished
* **Clearer version history**: Long change summaries now stay neatly inside their version-history entry, and an "Invalid Date" shown on Release-to-Live has been fixed
* **Checkbox subtitle branding**: You can now set and brand the subtitle text shown beneath a checkbox
* **Automatic SEO title**: New and cloned projects now use the project name as the page's SEO title automatically
* **Quicker API key refresh**: Regenerating a project's API key is now a refresh icon right inside the token field
## Client SDK
* **More resilient verification flows**: The client now detects when a browser extension or network blocks a verification request and recovers gracefully via a status fallback instead of stalling, surfacing a clear "connection blocked" message when it can't
* **Smoother camera capture**: Camera failures are now classified with an in-modal retry, and re-capturing a document or selfie skips the extra confirmation step
* **New input options**: File upload can pre-filter the picker to accepted file types, autocomplete fields can show an optional description line per option, and checkbox subtitles now sit correctly beneath the label
## Platform
* **New European hosting region**: A new Europe (GCP) region is now available for hosting production projects
# October 2026
Source: https://docs.zenoo.com/changelog/2026-10
Studio's interaction palette gained categories and stability badges, the field mapper can now step through lists, and a batch of UI polish landed alongside a new European hosting region and more reliable project builds.
## Platform
* **New European hosting region**: A Europe (GCP) region is now available for production projects
## Studio
* **Organised interaction palette**: Interactions in the flow editor are now grouped into a consistent set of categories, each with its own icon, so finding the right one as you build is faster and more predictable across both the default and vendor libraries
* **Interaction stability badges**: Integrations that aren't yet production-ready are now clearly marked with an **alpha** or **beta** badge, so you can tell at a glance which interactions are still maturing before adding them to a live flow
* **Map a list item from a value**: The field mapper can now use a source value to pick an index into a list, unlocking Retry-based loops that step through entries one at a time — for example, processing each applicant, document, or result in turn
* **Show/hide password toggle**: Login and credential fields now have a reveal toggle, so you can check what you've typed before submitting
* **Redesigned desktop preview**: The desktop ("laptop") project preview has been repaired and restyled as a clean macOS browser window, so desktop previews render correctly again
* **Smarter project search**: You can now find a project by typing part of its provision URL or ID, instead of needing the full value
* **Polished library icons**: Default-library icons have been standardised, with refreshed Refinitiv and GIACT marks
## Client SDK
* **More reliable project builds**: Resolved a build failure affecting projects that include pre-minified vendor assets, so those projects build cleanly again
# Alerts
Source: https://docs.zenoo.com/data-model/alerts
Complete field reference for the Alert resource in the Zenoo CLM API.
# Alerts
Alerts represent compliance issues that require analyst review. They are generated automatically from check results (screening matches, identity failures) or created manually. Each alert belongs to a case and optionally links to a specific entity.
## Resource fields
| Field | Type | Description |
| - | - | - |
| `token` | string | Unique alert identifier (prefix `alt_`) |
| `type` | string | Alert type. See [types](#alert-types) |
| `category` | string | Alert category. See [categories](#alert-categories) |
| `title` | string | Human-readable alert title |
| `description` | string | Detailed description with source and match details |
| `status` | string | Current status. See [statuses](#alert-statuses) |
| `priority` | string | Priority: `Critical`, `High`, `Medium`, `Low` |
| `priority_score` | number | Composite priority score (0-290) |
| `risk_tier` | string | Risk tier assigned by assessor: `High`, `Medium`, `Low` |
| `match_score` | number | Screening match confidence (0-100) |
| `case_token` | string | Parent case token |
| `entity_name` | string | Name of the entity this alert relates to |
| `entity_token` | string | Token of the related entity |
| `assigned_to` | object | Assigned analyst `{ id, name, email }` |
| `resolved_by` | object | User who resolved `{ id, name, email }` |
| `resolved_at` | string | ISO 8601 timestamp of resolution |
| `resolution_action` | string | Resolution action. See [resolution actions](#resolution-actions) |
| `resolution_notes` | string | Resolution justification |
| `sla_due_date` | string | ISO 8601 timestamp of SLA deadline |
| `sla_status` | string | SLA status: `On Track`, `Warning`, `Critical`, `Breached` |
| `sla_hours_remaining` | number | Hours until SLA breach |
| `source_data` | object | Raw screening match data from provider |
| `ai_research` | object | AI research results (see [AI fields](#ai-research-fields)) |
| `auto_triaged` | boolean | Whether auto-triage was attempted |
| `auto_disposed` | boolean | Whether the alert was auto-disposed |
| `auto_disposition_reason` | string | Detailed rationale for auto-disposition |
| `qa_sample` | boolean | Whether flagged for QA review |
| `created_at` | string | ISO 8601 timestamp of creation |
| `updated_at` | string | ISO 8601 timestamp of last modification |
## AI research fields
The `ai_research` object is populated when AI analysis is triggered:
| Field | Type | Description |
| - | - | - |
| `status` | string | `pending`, `processing`, `completed`, `failed` |
| `assessment` | string | Detailed narrative analysis of the match |
| `confidence` | number | AI confidence in its assessment (0-100) |
| `false_positive_probability` | number | Estimated false positive probability (0-100) |
| `recommended_action` | string | AI-suggested resolution action |
| `sources` | array | Array of `{ title, url }` sources consulted |
| `researched_at` | string | ISO 8601 timestamp of research completion |
## Alert statuses
| Status | Description |
| - | - |
| `Open` | Created, awaiting analyst review |
| `Acknowledged` | Analyst has claimed the alert |
| `Resolved` | Resolved with an action and notes |
| `False Positive` | Confirmed as a false positive match |
| `Escalated` | Escalated to a senior reviewer or manager |
## Alert types
| Type | Description |
| - | - |
| `Screening` | Generated from PEP/sanctions/adverse media checks |
| `Identity` | Generated from identity verification failures |
| `Company` | Generated from company registry check issues |
| `Document` | Generated from document verification failures |
| `Financial` | Generated from financial or credit check issues |
| `Client Input` | Created when client input is required |
| `Internal Review` | Created for internal compliance review |
## Alert categories
| Category | Type | Description |
| - | - | - |
| `PEP Match` | Screening | Politically Exposed Person match |
| `Sanctions Hit` | Screening | Sanctions list match |
| `Adverse Media` | Screening | Negative media coverage match |
| `Name Mismatch` | Identity | Name differs between documents |
| `DOB Mismatch` | Identity | Date of birth discrepancy |
| `Criminal Investigation` | Screening | Criminal investigation record |
| `Regulatory Action` | Screening | Regulatory enforcement action |
| `API Failure` | Company | External API check failure |
| `Watchlist Match` | Screening | Other watchlist match |
| `Ongoing Screening` | Screening | Hit from ongoing monitoring |
| `RCA Match` | Screening | Relative or close associate match |
## Resolution actions
| Action | Description |
| - | - |
| `Approve` | Alert reviewed, no issues or risk accepted |
| `Decline` | Confirmed compliance issue, reject entity |
| `Escalate` | Route to senior reviewer |
| `Request Document` | Request additional evidence from client |
| `Approve with Conditions` | Accept with caveats (e.g., monitoring) |
## Example
```json theme={null}
{
"token": "alt_001",
"type": "Screening",
"category": "PEP Match",
"title": "PEP Match: John Doe",
"description": "WorldCheck screening returned a PEP match for 'John Doe' against UK political figures database.",
"status": "Open",
"priority": "High",
"priority_score": 78,
"risk_tier": "Medium",
"match_score": 92,
"case_token": "cas_xyz789",
"entity_name": "John Doe",
"entity_token": "ent_ubo789",
"assigned_to": {
"id": "user_analyst01",
"name": "Sarah Johnson",
"email": "sarah@example.com"
},
"sla_due_date": "2026-02-18T10:00:00Z",
"sla_status": "On Track",
"sla_hours_remaining": 46.5,
"source_data": {
"provider": "WorldCheck",
"matched_name": "John Doe",
"match_type": "Exact",
"pep_category": "Former Local Councillor"
},
"ai_research": null,
"auto_triaged": false,
"auto_disposed": false,
"qa_sample": false,
"created_at": "2026-02-16T10:15:00Z",
"updated_at": "2026-02-16T10:15:00Z"
}
```
## Related endpoints
| Endpoint | Method | Description |
| - | - | - |
| `GET /v1/alerts` | GET | List alerts with filters |
| `GET /v1/alerts/{token}` | GET | Get alert details |
| `POST /v1/alerts/{token}/resolve` | POST | Resolve an alert |
| `POST /v1/alerts/{token}/acknowledge` | POST | Acknowledge an alert |
| `POST /v1/alerts/{token}/escalate` | POST | Escalate an alert |
| `POST /v1/alerts/{token}/ai-research` | POST | Trigger AI research |
| `GET /v1/alerts/{token}/ai-research` | GET | Get AI research results |
| `POST /v1/alerts/bulk/resolve` | POST | Bulk resolve alerts |
| `POST /v1/alerts/bulk/acknowledge` | POST | Bulk acknowledge alerts |
| `POST /v1/alerts/bulk/assign` | POST | Bulk assign alerts |
## See also
* [Alert Management Guide](/guides/alert-management)
* [AI Research Guide](/guides/ai-research)
* [Status Enums Reference](/reference/status-enums)
# Audit Logs
Source: https://docs.zenoo.com/data-model/audit-logs
Complete field reference for the Audit Log resource and all 32 event types in the Zenoo CLM API.
# Audit Logs
Audit log entries record every significant action in the platform. They are immutable -- once created, they cannot be edited or deleted. The audit trail is designed for regulatory compliance with FinCEN, OFAC, FCA, FATF, and EU AMLD5 requirements.
## Resource fields
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique audit log identifier |
| `event_type` | string | Event type code. See [event types](#event-types) |
| `severity` | string | Severity level: `INFO`, `WARNING`, `ERROR` |
| `description` | string | Human-readable description of the event |
| `old_value` | string | Previous value (for status changes, overrides) |
| `new_value` | string | New value after the change |
| `reason` | string | Justification or notes for the action |
| `case_token` | string | Related case token |
| `alert_token` | string | Related alert token |
| `entity_token` | string | Related entity token |
| `check_token` | string | Related check token |
| `actor` | object | User who performed the action (see Actor object) |
| `approved_by` | object | Approving user for four-eyes actions (see Actor object) |
| `approved_at` | string | ISO 8601 timestamp of approval |
| `metadata` | object | Additional context data (varies by event type) |
| `created_at` | string | ISO 8601 timestamp of the event |
## Actor object
| Field | Type | Description |
| - | - | - |
| `id` | string | User identifier |
| `name` | string | Display name (denormalized at write time) |
| `email` | string | Email address (denormalized at write time) |
Actor names and emails are stored at write time so they survive user deactivation. Historical audit records always show the name as it was when the action occurred.
## Event types
### Case events (5)
| Event Type | Severity | Description | Key fields |
| - | - | - | - |
| `CASE_CREATED` | INFO | New case created | `case_token`, type, priority |
| `CASE_STATUS_CHANGE` | INFO | Case status transition | `old_value`, `new_value` |
| `CASE_ASSIGNMENT` | INFO | Case assigned to a user | `new_value` (assignee) |
| `CASE_ESCALATION` | WARNING | Case escalated to a manager | `reason`, `new_value` (manager) |
| `CASE_CLOSURE` | INFO | Case closed | `reason` (resolution notes) |
### Alert events (7)
| Event Type | Severity | Description | Key fields |
| - | - | - | - |
| `ALERT_CREATED` | INFO | New alert generated | `alert_token`, category, type |
| `ALERT_ACKNOWLEDGED` | INFO | Alert claimed by analyst | `actor` |
| `ALERT_RESOLVED` | INFO | Alert resolved with action | `new_value` (action), `reason` (notes) |
| `ALERT_FALSE_POSITIVE` | INFO | Alert marked as false positive | `reason` (justification) |
| `ALERT_ESCALATED` | WARNING | Alert escalated to manager | `reason`, `new_value` (manager) |
| `ALERT_ASSIGNMENT` | INFO | Alert assigned to a user | `new_value` (assignee) |
| `ALERT_AUTO_DISPOSITION` | INFO | Alert auto-disposed by AI pipeline | `reason` (AI rationale) |
### Risk events (4)
| Event Type | Severity | Description | Key fields |
| - | - | - | - |
| `RISK_ASSESSMENT_CREATED` | INFO | New risk assessment initiated | `case_token`, source |
| `RISK_SCORE_CALCULATED` | INFO | Risk dimensions scored | `new_value` (tier), metadata (scores) |
| `RISK_TIER_OVERRIDE` | WARNING | Analyst overrode calculated tier | `old_value`, `new_value`, `reason` |
| `RISK_ASSESSMENT_APPROVED` | INFO | Assessment approved by reviewer | `approved_by`, `approved_at` |
### Check events (4)
| Event Type | Severity | Description | Key fields |
| - | - | - | - |
| `CHECK_STATUS_CHANGE` | INFO | Check status transition | `old_value`, `new_value` |
| `CHECK_WAIVED` | WARNING | Check waived by analyst | `reason`, `actor` |
| `CHECK_REJECTED` | WARNING | Check result rejected | `reason` |
| `CHECK_COMPLETED` | INFO | Check execution completed | `new_value` (result) |
### Document events (2)
| Event Type | Severity | Description | Key fields |
| - | - | - | - |
| `DOCUMENT_UPLOADED` | INFO | Document uploaded by client or analyst | `metadata` (doc type) |
| `DOCUMENT_REVIEWED` | INFO | Document reviewed (accepted or declined) | `new_value` (decision) |
### Reviewer events (3)
| Event Type | Severity | Description | Key fields |
| - | - | - | - |
| `REVIEWER_ADDED` | INFO | Reviewer assigned to alert or case | `new_value` (reviewer), role |
| `REVIEWER_REMOVED` | INFO | Reviewer removed from alert or case | `old_value` (reviewer) |
| `REVIEWER_RESPONSE` | INFO | Reviewer approved or declined | `new_value` (decision) |
### Entity events (2)
| Event Type | Severity | Description | Key fields |
| - | - | - | - |
| `ENTITY_ADDED` | INFO | Entity added to a case | `entity_token`, role |
| `ENTITY_REMOVED` | INFO | Entity removed from a case | `entity_token` |
### Other events (5)
| Event Type | Severity | Description | Key fields |
| - | - | - | - |
| `COMMENT_ADDED` | INFO | Comment added to alert or case | `metadata` (comment type) |
| `AUTO_ESCALATION` | WARNING | System auto-escalated due to SLA breach | `reason`, `new_value` |
| `SLA_BREACH` | ERROR | SLA deadline passed without resolution | `case_token` |
| `BULK_OPERATION` | INFO | Bulk action performed on multiple records | `metadata` (count, action) |
| `AI_ANALYSIS` | INFO | AI research or analysis completed | `alert_token`, `metadata` |
## Example
```json theme={null}
{
"id": "log_001",
"event_type": "ALERT_RESOLVED",
"severity": "INFO",
"description": "Alert resolved: Approve",
"old_value": "Open",
"new_value": "Resolved",
"reason": "False positive confirmed. Matched entity is unrelated to subject.",
"case_token": "cas_xyz789",
"alert_token": "alt_001",
"entity_token": "ent_ubo789",
"check_token": null,
"actor": {
"id": "user_analyst01",
"name": "Sarah Johnson",
"email": "sarah@example.com"
},
"approved_by": null,
"approved_at": null,
"metadata": {
"resolution_action": "Approve",
"ai_confidence": 85,
"false_positive_probability": 82
},
"created_at": "2026-02-16T11:30:00Z"
}
```
## Related endpoints
| Endpoint | Method | Description |
| - | - | - |
| `GET /v1/audit-logs` | GET | List audit logs with filters |
| `GET /v1/cases/{token}/audit-logs` | GET | List audit logs for a case |
| `GET /v1/cases/{token}/audit-logs/export` | GET | Export audit logs as CSV |
### Filter parameters
| Parameter | Type | Description |
| - | - | - |
| `event_type` | string | Filter by event type code |
| `severity` | string | Filter by severity: `INFO`, `WARNING`, `ERROR` |
| `from` | string | Start date (ISO 8601) |
| `to` | string | End date (ISO 8601) |
| `actor_id` | string | Filter by acting user |
| `limit` | number | Results per page (default 50, max 200) |
| `cursor` | string | Pagination cursor |
## See also
* [Audit Trail Guide](/guides/audit-trail)
* [Status Enums Reference](/reference/status-enums)
# Cases
Source: https://docs.zenoo.com/data-model/cases
Complete field reference for the Case resource in the Zenoo CLM API.
# Cases
A case represents an AML investigation into an entity. Cases aggregate alerts, risk assessments, verification checks, and reviewer workflows.
## Resource fields
| Field | Type | Description |
| - | - | - |
| `token` | string | Unique case identifier (prefix `cas_`) |
| `name` | string | Auto-generated case reference (e.g., `AML-2026-0042`) |
| `type` | string | Case type: `Onboarding`, `Review`, `Perpetual` |
| `status` | string | Current status. See [Status Enums](/reference/status-enums) |
| `priority` | string | Priority level: `Critical`, `High`, `Medium`, `Low` |
| `risk_tier` | string | Overall risk tier: `High`, `Medium`, `Low` |
| `risk_score` | number | Numeric risk score (0-100) |
| `entity_token` | string | Token of the primary entity under investigation |
| `entity_name` | string | Name of the primary entity |
| `entity_type` | string | Entity type: `company` or `person` |
| `assigned_to` | object | Assigned analyst (see User object below) |
| `escalated_to` | object | Manager the case is escalated to (see User object) |
| `escalation_date` | string | ISO 8601 timestamp of escalation |
| `escalation_reason` | string | Reason for escalation |
| `start_date` | string | ISO 8601 timestamp of case creation |
| `due_date` | string | ISO 8601 date of SLA deadline |
| `completed_at` | string | ISO 8601 timestamp of case closure |
| `resolution_notes` | string | Resolution summary written at closure |
| `open_alerts_count` | number | Count of unresolved alerts on this case |
| `external_reference` | string | Your external reference ID for idempotency |
| `sla_status` | string | SLA status: `On Track`, `Warning`, `Critical`, `Breached` |
| `checks_summary` | object | Summary of check outcomes (see below) |
| `risk_assessment` | object | Latest risk assessment (see below) |
| `created_at` | string | ISO 8601 timestamp of creation |
| `updated_at` | string | ISO 8601 timestamp of last modification |
## Nested objects
### User object
| Field | Type | Description |
| - | - | - |
| `id` | string | User identifier |
| `name` | string | Display name |
| `email` | string | Email address |
### Checks summary object
| Field | Type | Description |
| - | - | - |
| `total` | number | Total checks created |
| `passed` | number | Checks with `Pass` result |
| `referred` | number | Checks with `Refer` result |
| `failed` | number | Checks with `Fail` result |
| `waived` | number | Checks manually waived |
| `reused` | number | Checks reused from other cases |
| `pending` | number | Checks still in progress or pending |
### Risk assessment object
| Field | Type | Description |
| - | - | - |
| `overall_score` | number | Calculated overall risk score (0-100) |
| `tier` | string | Overall tier: `High`, `Medium`, `Low` |
| `customer_risk` | object | Customer dimension `{ score, tier }` |
| `geographic_risk` | object | Geographic dimension `{ score, tier }` |
| `product_risk` | object | Product/Service dimension `{ score, tier }` |
| `channel_risk` | object | Channel dimension `{ score, tier }` |
| `override_tier` | string | Analyst override tier (if applicable) |
| `override_reason` | string | Override justification |
| `edd_required` | boolean | Whether EDD is required based on risk |
## Example
```json theme={null}
{
"token": "cas_xyz789",
"name": "AML-2026-0042",
"type": "Onboarding",
"status": "In Progress",
"priority": "Medium",
"risk_tier": "Medium",
"risk_score": 45,
"entity_token": "ent_abc123",
"entity_name": "Acme Holdings Ltd",
"entity_type": "company",
"assigned_to": {
"id": "user_analyst01",
"name": "Sarah Johnson",
"email": "sarah@example.com"
},
"escalated_to": null,
"escalation_date": null,
"start_date": "2026-02-16T10:01:00Z",
"due_date": "2026-03-02",
"completed_at": null,
"resolution_notes": null,
"open_alerts_count": 1,
"external_reference": "YOUR-REF-2026-0042",
"sla_status": "On Track",
"checks_summary": {
"total": 6,
"passed": 3,
"referred": 1,
"failed": 0,
"waived": 0,
"reused": 0,
"pending": 2
},
"created_at": "2026-02-16T10:01:00Z",
"updated_at": "2026-02-16T11:00:00Z"
}
```
## Related endpoints
| Endpoint | Method | Description |
| - | - | - |
| `GET /v1/cases` | GET | List cases with filters |
| `POST /v1/cases` | POST | Create a new case |
| `GET /v1/cases/{token}` | GET | Get case details |
| `POST /v1/cases/{token}/close` | POST | Close a case |
| `POST /v1/cases/{token}/escalate` | POST | Escalate a case |
| `GET /v1/cases/{token}/alerts` | GET | List alerts for a case |
| `GET /v1/cases/{token}/checks` | GET | List checks for a case |
| `GET /v1/cases/{token}/entities` | GET | List entities for a case |
| `GET /v1/cases/{token}/audit-logs` | GET | List audit logs for a case |
| `GET /v1/cases/{token}/comments` | GET | List comments for a case |
| `GET /v1/cases/{token}/compliance-report` | GET | Generate compliance report |
## See also
* [Case Lifecycle Guide](/guides/case-lifecycle)
* [Status Enums Reference](/reference/status-enums)
# Check Types
Source: https://docs.zenoo.com/data-model/check-types
Complete registry of verification check codes, execution methods, and status lifecycle.
# Check Types
Every verification Zenoo performs is composed of individual checks. Each check has a unique code, a method of execution, and a status lifecycle. The `checks_summary.checks[]` array in the [compliance report](/data-model/compliance-report) references these types.
## Identity Checks
Verify an individual's identity through document scanning, biometrics, and database lookups.
| Code | Name | Method | Description |
| - | - | - | - |
| `IDV_DOCUMENT` | Identity Document | API | Government ID scanning with OCR extraction. Supports passports, national ID cards, and driving licences. Returns extracted name, DOB, expiry, and gender |
| `IDV_BIOMETRIC` | Biometric Verification | API | Selfie capture with liveness detection and face matching against the document photo. Detects spoofing attempts (photos of photos, masks, screen recordings) |
| `IDV_DATABASE` | Database Verification | API | Name, date of birth, and address validation against official databases (electoral roll, credit bureau, government records) |
| `IDV_ADDRESS` | Address Verification | API | Address validation and geocoding against postal reference databases. Returns match level: full, partial, or none |
| `IDV_PHONE` | Phone Verification | API | Phone number verification including carrier lookup, line type detection (mobile, landline, VoIP), and OTP validation |
| `IDV_EMAIL` | Email Verification | API | Email deliverability check, disposable email detection, and fraud risk scoring |
## Screening Checks
Screen entities against global PEP, sanctions, adverse media, and watchlist databases.
| Code | Name | Method | Description |
| - | - | - | - |
| `SCR_PEP` | PEP Screening | API | Screens against Politically Exposed Person databases. Returns match score, PEP class (1-4), positions held, and known associates |
| `SCR_SANCTIONS` | Sanctions Screening | API | Screens against OFAC SDN, EU, UN, and UK HMT consolidated sanctions lists. Any hit requires immediate escalation |
| `SCR_ADVERSE_MEDIA` | Adverse Media | API | Screens against negative news databases covering financial crime, fraud, terrorism, and money laundering. Returns article titles, URLs, and snippets |
| `SCR_WATCHLIST` | Watchlist Screening | API | Screens against law enforcement databases including Interpol, FBI, and national fugitive lists |
## Company Checks
Verify company identity and extract corporate structure from official registries.
| Code | Name | Method | Description |
| - | - | - | - |
| `KYB_REGISTRATION` | Company Registration | API | Verifies the company against the official registry. Returns legal name, status, incorporation date, registered address, and SIC codes |
| `KYB_DIRECTORS` | Directors Verification | API | Extracts and verifies the current directors from the company registry. Each director is automatically screened for PEP/sanctions |
| `KYB_UBO` | UBO Verification | API | Extracts beneficial owners (shareholders with significant control) from the company registry. Each UBO is automatically screened |
## Financial Checks
Verify financial standing and source of funds/wealth.
| Code | Name | Method | Description |
| - | - | - | - |
| `FIN_CREDIT` | Credit Check | API | Credit history verification against credit bureau records |
| `FIN_BANK_ACCOUNT` | Bank Account Verification | Document | Bank statement upload and verification. Confirms account ownership and recent transaction activity |
| `FIN_SOF` | Source of Funds | Document | Source of funds documentation review. Required for high-value transactions or high-risk entities |
| `FIN_SOW` | Source of Wealth | Document | Source of wealth documentation review. Required for Enhanced Due Diligence (EDD) |
## Check Status Lifecycle
Every check moves through a defined status lifecycle:
```
Pending -> Queued -> In Progress -> Complete
-> Failed
-> Waived
-> Cancelled
-> Reused
```
| Status | Description |
| - | - |
| `Pending` | Check has been created but not yet queued for execution |
| `Queued` | Check is queued and waiting for execution |
| `In Progress` | Check is actively running against the provider |
| `Awaiting Document` | Check requires a document upload before it can proceed |
| `Awaiting Client` | Check requires user interaction (e.g., Person Verification journey completion) |
| `Complete` | Check finished successfully with a result |
| `Failed` | Check could not be completed (provider error, timeout, invalid data) |
| `Waived` | Check was manually waived by a compliance analyst |
| `Cancelled` | Check was cancelled before execution |
| `Reused` | A valid result from a previous verification was applied instead of re-executing |
| `Retry Scheduled` | Check failed with a transient error and is scheduled for automatic retry |
## Check Categories
Checks are grouped into five categories for reporting and risk assessment:
| Category | Check Codes | Description |
| - | - | - |
| **Identity** | `IDV_DOCUMENT`, `IDV_BIOMETRIC`, `IDV_DATABASE`, `IDV_ADDRESS`, `IDV_PHONE`, `IDV_EMAIL` | Individual identity verification |
| **Screening** | `SCR_PEP`, `SCR_SANCTIONS`, `SCR_ADVERSE_MEDIA`, `SCR_WATCHLIST` | PEP, sanctions, and adverse media screening |
| **Company** | `KYB_REGISTRATION`, `KYB_DIRECTORS`, `KYB_UBO` | Company registry verification |
| **Financial** | `FIN_CREDIT`, `FIN_BANK_ACCOUNT`, `FIN_SOF`, `FIN_SOW` | Financial standing and source of funds |
| **EDD** | Varies | Enhanced Due Diligence checks triggered by high-risk assessment |
## Execution Methods
| Method | Description | User Interaction |
| - | - | - |
| **API** | Executed automatically via provider API. No user action needed | None |
| **Document** | Requires document upload (bank statement, proof of funds) | Document upload |
| **Manual** | Requires manual review by a compliance analyst | Analyst review |
| **Hybrid** | Combines API execution with manual review for edge cases | Varies |
| **Webhook** | Results received via webhook callback from an external provider | None |
API checks execute automatically when the verification is initiated. Document checks wait for the required document to be uploaded. Manual checks are assigned to compliance analysts for review.
# Checks
Source: https://docs.zenoo.com/data-model/checks
Complete field reference for the Check resource in the Zenoo CLM API.
# Checks
Checks represent verification requirements executed against external providers. They are auto-created when entities are added to a case and track the full lifecycle from creation through execution to result processing.
## Resource fields
| Field | Type | Description |
| - | - | - |
| `token` | string | Unique check identifier (prefix `chk_`) |
| `type` | string | Check type name (e.g., `PEP Screening`, `Company Registry`) |
| `type_code` | string | Developer name from check type configuration |
| `category` | string | Check category. See [categories](#check-categories) |
| `status` | string | Current status. See [statuses](#check-statuses) |
| `result` | string | Check result: `Pass`, `Refer`, `Fail`, `null` (pending) |
| `result_summary` | string | Human-readable result description |
| `result_data` | object | Raw JSON result from API provider |
| `match_score` | number | Screening match confidence (0-100), null if not applicable |
| `case_token` | string | Parent case token |
| `entity_name` | string | Name of the entity being checked |
| `entity_token` | string | Token of the entity being checked |
| `provider` | string | Provider code (e.g., `WorldCheck`, `Kyckr`, `Twilio`) |
| `execution_method` | string | Execution method. See [execution methods](#execution-methods) |
| `alert_token` | string | Token of generated alert (if result produced an alert) |
| `retry_count` | number | Number of execution retry attempts |
| `edd_only` | boolean | Whether this check only applies to Enhanced Due Diligence |
| `verification_url` | string | Client verification URL (async checks only) |
| `verification_expires` | string | ISO 8601 expiry of verification URL |
| `valid_until` | string | ISO 8601 date until which this check is valid for reuse |
| `original_check_token` | string | Token of the original check (for reused checks) |
| `reuse_source_case` | string | Case name the reused check originated from |
| `document_token` | string | Token of linked document requirement (hybrid checks) |
| `started_at` | string | ISO 8601 timestamp of execution start |
| `completed_at` | string | ISO 8601 timestamp of execution completion |
| `created_at` | string | ISO 8601 timestamp of creation |
| `updated_at` | string | ISO 8601 timestamp of last modification |
## Check statuses
| Status | Description |
| - | - |
| `Pending` | Created, awaiting execution or manual action |
| `Queued` | Scheduled for automatic execution |
| `In Progress` | Currently executing against a provider |
| `Awaiting Document` | Hybrid check waiting for document upload |
| `Awaiting Client` | Waiting for client-side action (document capture, etc.) |
| `Complete` | Execution finished with a result |
| `Failed` | Execution error or timeout |
| `Waived` | Manually waived by analyst with documented reason |
| `Cancelled` | Cancelled before execution |
| `Reused` | Valid check from another case reused |
| `Retry Scheduled` | Failed check scheduled for automatic retry |
## Check categories
| Category | Description |
| - | - |
| `Screening` | PEP, sanctions, adverse media, watchlist checks |
| `Identity` | Identity and biometric verification |
| `Company` | Company registry and corporate checks |
| `Financial` | Credit, source of wealth, financial checks |
| `EDD` | Enhanced Due Diligence checks |
## Check results
| Result | Description | Alert generated? |
| - | - | - |
| `Pass` | Verification passed, no issues | No |
| `Refer` | Potential issue, needs review | Yes |
| `Fail` | Verification failed | Yes |
## Execution methods
| Method | Description | Example provider |
| - | - | - |
| `API` | Automated execution via external API | WorldCheck, Kyckr |
| `Manual_Check` | Manual review by analyst | Manual sanctions |
| `Document` | Document upload required | Certificate upload |
| `Free_Text` | Free text input required | SOW explanation |
| `Dropdown` | Selection from predefined options | SOW selection |
| `Hybrid` | API check paired with document requirement | Registry + COI |
## Example
```json theme={null}
{
"token": "chk_001",
"type": "PEP Screening",
"type_code": "PEP_SCREEN",
"category": "Screening",
"status": "Complete",
"result": "Refer",
"result_summary": "PEP match found: former local councillor (UK)",
"result_data": {
"matched_name": "John Doe",
"match_type": "Exact",
"pep_category": "Former Local Councillor",
"country": "GB",
"source_list": "WorldCheck PEP Database"
},
"match_score": 92,
"case_token": "cas_xyz789",
"entity_name": "John Doe",
"entity_token": "ent_ubo789",
"provider": "WorldCheck",
"execution_method": "API",
"alert_token": "alt_001",
"retry_count": 0,
"edd_only": false,
"valid_until": "2026-05-16",
"started_at": "2026-02-16T10:10:00Z",
"completed_at": "2026-02-16T10:10:12Z",
"created_at": "2026-02-16T10:05:00Z",
"updated_at": "2026-02-16T10:10:12Z"
}
```
## Related endpoints
| Endpoint | Method | Description |
| - | - | - |
| `GET /v1/cases/{token}/checks` | GET | List checks for a case |
| `GET /v1/checks/{token}` | GET | Get check details |
| `POST /v1/checks/{token}/execute` | POST | Manually trigger check execution |
| `POST /v1/checks/{token}/waive` | POST | Waive a check with reason |
| `POST /v1/checks/{token}/retry` | POST | Retry a failed check |
| `GET /v1/entities/{token}/checks` | GET | List checks for an entity |
## See also
* [Check Orchestration Guide](/guides/check-orchestration)
* [Status Enums Reference](/reference/status-enums)
# Comments
Source: https://docs.zenoo.com/data-model/comments
Complete field reference for the Comment resource in the Zenoo CLM API.
# Comments
Comments provide a threaded discussion system attached to cases, alerts, and checks. They support @mentions, emoji reactions, threading, and system-generated comments for audit compliance.
## Resource fields
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique comment identifier |
| `body` | string | Comment text content |
| `type` | string | Comment type. See [comment types](#comment-types) |
| `author` | object | Comment author (see Author object below) |
| `case_token` | string | Parent case token (if attached to a case) |
| `alert_token` | string | Parent alert token (if attached to an alert) |
| `check_token` | string | Parent check token (if attached to a check) |
| `parent_comment_id` | string | Parent comment ID for threaded replies |
| `reply_count` | number | Number of direct replies to this comment |
| `reactions` | array | Emoji reactions (see Reaction object below) |
| `mentioned_users` | array | Users @mentioned in this comment |
| `is_system` | boolean | Whether this is a system-generated comment |
| `created_at` | string | ISO 8601 timestamp of creation |
## Nested objects
### Author object
| Field | Type | Description |
| - | - | - |
| `id` | string | User identifier |
| `name` | string | Display name |
| `email` | string | Email address |
| `photo_url` | string | Profile photo URL |
| `initials` | string | Initials derived from name |
### Reaction object
| Field | Type | Description |
| - | - | - |
| `emoji` | string | Emoji character |
| `count` | number | Total reactions with this emoji |
| `user_reacted` | boolean | Whether the current user reacted with this |
| `user_names` | array | Names of users who reacted |
## Comment types
| Type | Description |
| - | - |
| `Comment` | Standard user comment |
| `Decision` | Compliance decision recorded as a comment |
| `Note` | Internal note (not visible to external reviewers) |
| `System` | System-generated comment (status changes, automation) |
## Example
```json theme={null}
{
"id": "cmt_001",
"body": "Reviewed the PEP match. John Doe was a local councillor from 2010-2014. No current political exposure. @sarah.johnson can you confirm the SOW documentation?",
"type": "Comment",
"author": {
"id": "user_analyst01",
"name": "Alex Thompson",
"email": "alex@example.com",
"photo_url": "https://example.com/photos/alex.jpg",
"initials": "AT"
},
"case_token": "cas_xyz789",
"alert_token": "alt_001",
"check_token": null,
"parent_comment_id": null,
"reply_count": 2,
"reactions": [
{
"emoji": "👍",
"count": 2,
"user_reacted": false,
"user_names": ["Sarah Johnson", "Mike Chen"]
}
],
"mentioned_users": [{ "id": "user_sarah01", "name": "Sarah Johnson" }],
"is_system": false,
"created_at": "2026-02-16T11:00:00Z"
}
```
## System comment example
```json theme={null}
{
"id": "cmt_sys_001",
"body": "Alert status changed from Open to Resolved. Action: Approve.",
"type": "System",
"author": {
"id": "system",
"name": "System",
"initials": "SY"
},
"alert_token": "alt_001",
"parent_comment_id": null,
"reply_count": 0,
"reactions": [],
"is_system": true,
"created_at": "2026-02-16T11:30:00Z"
}
```
## Threading
Comments support one level of nesting. Replies reference the parent comment via `parent_comment_id`. Retrieve replies for a comment:
```bash theme={null}
curl -X GET "https://api.zenoo.com/v1/comments/cmt_001/replies" \
-H "Authorization: Bearer your-api-key"
```
## Notifications
The comment system sends notifications for:
| Event | Notification type | Recipients |
| - | - | - |
| @mention | Push notification | Mentioned users |
| Reply to comment | Push notification | Parent comment author |
| Comment created | Platform Event | All subscribers (real-time) |
## Related endpoints
| Endpoint | Method | Description |
| - | - | - |
| `GET /v1/cases/{token}/comments` | GET | List comments for a case |
| `GET /v1/alerts/{token}/comments` | GET | List comments for an alert |
| `POST /v1/cases/{token}/comments` | POST | Add a comment to a case |
| `POST /v1/alerts/{token}/comments` | POST | Add a comment to an alert |
| `GET /v1/comments/{id}/replies` | GET | Get replies for a comment |
| `POST /v1/comments/{id}/reactions` | POST | Add a reaction to a comment |
| `DELETE /v1/comments/{id}/reactions` | DELETE | Remove a reaction from a comment |
## See also
* [Case Lifecycle Guide](/guides/case-lifecycle)
* [Alert Management Guide](/guides/alert-management)
# Company Verification
Source: https://docs.zenoo.com/data-model/company-verification
Company verification results from registry lookups including the verified legal entity, directors, and beneficial owners.
# Company Verification
Company verification results come from registry lookups and include the verified legal entity, extracted directors, and discovered beneficial owners. These fields appear in the `company`, `directors[]`, and `beneficial_owners[]` sections of the [compliance report](/data-model/compliance-report).
## Company Object
The `company` object contains registry-verified data about the legal entity.
| Field | Type | Description |
| - | - | - |
| `legal_name` | string | Verified legal name from the company registry |
| `registration_number` | string | Verified registration number |
| `jurisdiction` | string | Country of incorporation (ISO 3166-1 alpha-2) |
| `company_status` | string | Current status: `Active`, `Dissolved`, `Struck Off` |
| `company_type` | string | Legal entity type: `Private Limited Company`, `PLC`, `LLP`, etc. |
| `incorporation_date` | string (date) | Date of formation, ISO 8601 |
| `registered_address` | string | Official registered address as a single formatted string |
| `sic_codes` | string | Industry classification codes, comma-separated |
| `verification_status` | string | `Verified`, `Failed`, or `Pending` |
| `verified_at` | string (datetime) | ISO 8601 timestamp of when registry verification completed |
### Example
```json theme={null}
{
"company": {
"legal_name": "Acme Holdings Limited",
"registration_number": "12345678",
"jurisdiction": "GB",
"company_status": "Active",
"company_type": "Private Limited Company",
"incorporation_date": "2015-03-15",
"registered_address": "123 High Street, London, EC1A 1BB",
"sic_codes": "64110,64191",
"verification_status": "Verified",
"verified_at": "2026-01-15T14:30:15Z"
}
}
```
## Director Results
The `directors[]` array contains individuals extracted from the company registry and screened for PEP/sanctions exposure. Directors may include those you submitted in the request plus additional directors discovered from the registry.
| Field | Type | Description |
| - | - | - |
| `name` | string | Full name |
| `date_of_birth` | string (date) | Date of birth, if available from registry or input |
| `role` | string | Role: `Director`, `Secretary`, `Chair`, etc. |
| `pep_status` | string | `No Hit` or `Hit` |
| `sanctions_status` | string | `No Hit` or `Hit` |
| `screening_completed_at` | string (datetime) | Timestamp of when screening finished |
### Example
```json theme={null}
{
"directors": [
{
"name": "Jane Smith",
"date_of_birth": "1980-05-20",
"role": "Director",
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"screening_completed_at": "2026-01-15T14:30:25Z"
},
{
"name": "Robert Williams",
"date_of_birth": "1972-08-03",
"role": "Secretary",
"pep_status": "Hit",
"sanctions_status": "No Hit",
"screening_completed_at": "2026-01-15T14:30:26Z"
}
]
}
```
A `pep_status` of `Hit` means the individual matched a PEP record in the screening database. Check the [screening results](/data-model/screening-results) for match details including score, category, and positions held.
## Beneficial Owner Results
The `beneficial_owners[]` array contains UBOs extracted from the company registry or provided in your request. Each UBO is screened for PEP/sanctions exposure.
| Field | Type | Description |
| - | - | - |
| `name` | string | Full name |
| `date_of_birth` | string (date) | Date of birth, if available |
| `nationality` | string | ISO 3166-1 alpha-2 country code |
| `ownership_percentage` | number | Ownership stake as a percentage (0-100) |
| `pep_status` | string | `No Hit` or `Hit` |
| `sanctions_status` | string | `No Hit` or `Hit` |
| `identity_verified` | boolean | Whether identity verification (Person Verification) has been completed for this individual |
| `verification_date` | string (datetime) | Timestamp of identity verification, if completed |
### Example
```json theme={null}
{
"beneficial_owners": [
{
"name": "John Doe",
"date_of_birth": "1975-11-10",
"nationality": "GB",
"ownership_percentage": 75,
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"identity_verified": false
},
{
"name": "Maria Garcia",
"date_of_birth": "1983-02-28",
"nationality": "ES",
"ownership_percentage": 25,
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"identity_verified": true,
"verification_date": "2026-01-10T09:15:00Z"
}
]
}
```
## Registry Source
Company data is sourced from official government company registries via the provider configured for your project. Coverage includes:
* United Kingdom (Companies House)
* Ireland (CRO)
* EU member states
* United States (state-level registries)
* 120+ additional jurisdictions
Registry data reflects the latest filing at the time of the check. If a company's registry record has not been updated recently, some fields (like director appointments or address changes) may be outdated.
## Data Reconciliation
When you provide company data in the request and Zenoo retrieves data from the registry, both versions are available. The `company` object in the response always contains the **registry-verified** data. If there are discrepancies between your input and the registry (e.g., different address, mismatched company name), these are flagged as risk factors in the [risk assessment](/data-model/risk-assessment).
## Person Verification for Directors and UBOs
Company Verification automatically screens all directors and beneficial owners for PEP/sanctions/adverse media. However, if your compliance policy requires **identity verification** (document check + biometric liveness) for specific individuals — typically UBOs above a certain ownership threshold — you must initiate separate Person Verification flows.
### When to trigger Person Verification
| Individual | Trigger Condition | Verification Type |
| - | - | - |
| UBO ≥25% ownership | Always recommended | `/kyc/init` (async, user-facing) |
| UBO with PEP/sanctions hit | Required | `/kyc/init` (async, user-facing) |
| Director (high-risk jurisdiction) | Policy-dependent | `/kyc/init` or `/kyc/api` |
| Director (standard) | Optional | `/kyc/api` (sync, database only) |
### Example flow
```bash theme={null}
# 1. Company Verification returns UBO needing identity check
# beneficial_owners[0].identity_verified = false
# 2. Initiate Person Verification for that UBO
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/kyc/init" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-d '{
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1975-11-10",
"country": "GB",
"external_reference": "ONBOARD-2026-0042-UBO-001"
}'
# 3. Send the verification URL to the UBO
# https://instance.prod.onboardapp.io/{project_hash}/?t={start_token}
```
Use the `external_reference` field to link the Person Verification back to the original Company Verification. A recommended pattern: `{company_external_ref}-UBO-{index}` or `{company_external_ref}-DIR-{index}`.
See [Company-to-Person Verification Flow](/data-model/entities#company-to-person-verification-flow) for the full end-to-end flow.
# Compliance Report
Source: https://docs.zenoo.com/data-model/compliance-report
The top-level response from Zenoo containing the overall verification verdict, risk assessment, and detailed check results.
# Compliance Report
The compliance report is the top-level response from Zenoo's pull endpoint or webhook push. It contains the overall verification verdict, risk assessment, and detailed results for every check performed.
## Top-Level Fields
| Field | Type | Description |
| - | - | - |
| `case_reference` | string | Zenoo's unique case identifier (e.g., `AML-2026-0042`) |
| `external_reference` | string | Your reference ID, echoed back from the request |
| `processing_status` | string | `complete`, `processing`, or `failed` |
| `overall_verdict` | string | `Pass`, `Refer`, or `Fail` |
| `risk_tier` | string | `High`, `Medium`, or `Low` |
| `completed_at` | string (datetime) | ISO 8601 timestamp when all checks finished |
| `next_review_date` | string (date) | Recommended date for the next periodic review |
Store the `case_reference` alongside your internal record. You will need it for support queries, audit trails, and linking future verification updates.
## Company Verification Compliance Report
Full example for a company verification:
```json theme={null}
{
"case_reference": "AML-2026-0042",
"external_reference": "ONBOARD-2026-0042",
"processing_status": "complete",
"overall_verdict": "Refer",
"risk_tier": "Medium",
"completed_at": "2026-01-15T14:32:00Z",
"next_review_date": "2027-01-15",
"company": {
"legal_name": "Acme Holdings Limited",
"registration_number": "12345678",
"jurisdiction": "GB",
"company_status": "Active",
"company_type": "Private Limited Company",
"incorporation_date": "2015-03-15",
"registered_address": "123 High Street, London, EC1A 1BB",
"sic_codes": "64110,64191",
"verification_status": "Verified",
"verified_at": "2026-01-15T14:30:15Z"
},
"screening": {
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "Hit",
"adverse_media_count": 2,
"screening_provider": "WorldCheck",
"screening_completed_at": "2026-01-15T14:30:20Z"
},
"beneficial_owners": [
{
"name": "John Doe",
"date_of_birth": "1975-11-10",
"nationality": "GB",
"ownership_percentage": 75,
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"identity_verified": false
}
],
"directors": [
{
"name": "Jane Smith",
"date_of_birth": "1980-05-20",
"role": "Director",
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"screening_completed_at": "2026-01-15T14:30:25Z"
}
],
"risk_assessment": {
"overall_score": 45,
"tier": "Medium",
"factors": [
{
"factor": "Adverse Media",
"severity": "Medium",
"detail": "2 adverse media articles found for company"
}
]
},
"checks_summary": {
"total": 5,
"passed": 4,
"referred": 1,
"failed": 0,
"checks": [
{
"type": "Company Registration",
"status": "Pass",
"provider": "Registry"
},
{
"type": "Company Screening",
"status": "Refer",
"provider": "WorldCheck",
"matches": 2
},
{
"type": "Director Screening (Jane Smith)",
"status": "Pass",
"provider": "WorldCheck"
},
{
"type": "UBO Screening (John Doe)",
"status": "Pass",
"provider": "WorldCheck"
},
{
"type": "Directors Verification",
"status": "Pass",
"provider": "Registry"
}
]
},
"compliance_metadata": {
"cdd_completed": true,
"cdd_completed_at": "2026-01-15T14:32:00Z",
"edd_required": false,
"reviewed_by": "system",
"case_url": "https://app.zenoo.com/cases/AML-2026-0042"
}
}
```
## Person Verification Compliance Report
Full example for an individual verification:
```json theme={null}
{
"case_reference": "AML-2026-0099",
"external_reference": "APP-2026-0123",
"processing_status": "complete",
"overall_verdict": "Pass",
"risk_tier": "Low",
"completed_at": "2026-01-15T14:35:30Z",
"next_review_date": "2027-01-15",
"identity": {
"verified": true,
"decision": "Pass",
"score": 95,
"authentication_id": "auth-xyz789"
},
"document": {
"verified": true,
"document_type": "Passport",
"document_country": "GB",
"validation_result": "PASSED",
"tamper_result": "Pass",
"extracted_data": {
"full_name": "JANE ELIZABETH SMITH",
"date_of_birth": "1990-06-15",
"expiration_date": "2030-12-01",
"gender": "F"
}
},
"biometric": {
"liveness_verified": true,
"liveness_assessment": "Live",
"liveness_score": 98.5,
"face_match": true,
"face_match_score": 96.2
},
"screening": {
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "No Hit",
"screening_provider": "WorldCheck",
"screening_completed_at": "2026-01-15T14:35:10Z"
},
"phone_verification": {
"verified": true,
"carrier": "Vodafone UK",
"phone_type": "mobile"
},
"email_verification": {
"verified": true,
"risk_score": 12,
"deliverable": true
},
"address_verification": {
"verified": true,
"match_level": "full"
},
"risk_assessment": {
"overall_score": 15,
"tier": "Low",
"factors": []
},
"checks_summary": {
"total": 7,
"passed": 7,
"referred": 0,
"failed": 0,
"checks": [
{ "type": "Identity Document", "status": "Pass", "provider": "Zenoo" },
{ "type": "Biometric Liveness", "status": "Pass", "provider": "Zenoo" },
{
"type": "PEP/Sanctions Screening",
"status": "Pass",
"provider": "WorldCheck"
},
{ "type": "Phone Verification", "status": "Pass", "provider": "Zenoo" },
{ "type": "Email Verification", "status": "Pass", "provider": "Zenoo" },
{ "type": "Address Verification", "status": "Pass", "provider": "Zenoo" },
{ "type": "Database Verification", "status": "Pass", "provider": "Zenoo" }
]
},
"compliance_metadata": {
"cdd_completed": true,
"cdd_completed_at": "2026-01-15T14:35:30Z",
"edd_required": false,
"reviewed_by": "system",
"case_url": "https://app.zenoo.com/cases/AML-2026-0099"
}
}
```
## Response Field Reference
### Top-Level
| Field | Type | Values | Description |
| - | - | - | - |
| `case_reference` | string | -- | Zenoo's unique case ID |
| `external_reference` | string | -- | Your reference, echoed back |
| `processing_status` | string | `complete`, `processing`, `failed` | Current processing state |
| `overall_verdict` | string | `Pass`, `Refer`, `Fail` | Aggregated verification outcome |
| `risk_tier` | string | `High`, `Medium`, `Low` | Assessed risk level |
| `completed_at` | datetime | -- | ISO 8601 completion timestamp |
| `next_review_date` | date | -- | Recommended next review date |
### Compliance Metadata
| Field | Type | Description |
| - | - | - |
| `compliance_metadata.cdd_completed` | boolean | Whether Customer Due Diligence was performed |
| `compliance_metadata.cdd_completed_at` | datetime | CDD completion timestamp |
| `compliance_metadata.edd_required` | boolean | Whether Enhanced Due Diligence was triggered |
| `compliance_metadata.reviewed_by` | string | `system` for automated review, or analyst name |
| `compliance_metadata.case_url` | string (uri) | Direct link to the case in the Zenoo dashboard |
## Verdict Decision Logic
Use `overall_verdict` and `risk_tier` to drive your enrichment pipeline:
| Verdict | Risk Tier | Recommended Action |
| - | - | - |
| `Pass` | Low | Auto-approve |
| `Pass` | Medium | Auto-approve with enhanced monitoring |
| `Refer` | Medium | Manual compliance review required |
| `Refer` | High | Senior compliance review, consider EDD |
| `Fail` | Any | Reject and escalate to compliance team |
If `sanctions_status` is `Hit` on any entity (company, director, UBO, or individual), halt processing immediately regardless of the overall verdict.
# Enrichments
Source: https://docs.zenoo.com/data-model/enrichments
Automatic data enrichments applied to every verification including UBO discovery, document forensics, and risk scoring.
# Enrichments
Zenoo automatically enriches every verification with additional intelligence. These enrichments run without extra configuration or API calls. The enriched data appears in the pull response and webhook events.
## Enrichment Summary
| Enrichment | What It Does | Triggered By |
| - | - | - |
| UBO Discovery | Discovers beneficial owners from the company registry | Company Verification registry check |
| Company Intelligence | AI-powered company research and risk insight generation | Company Verification flow |
| Document Forensics | Tamper detection and fraud analysis on identity documents | Person Verification document upload |
| Risk Model Evaluation | Multi-dimensional risk scoring across four risk dimensions | All flows |
| Alert Generation | Creates compliance alerts from screening matches | Screening matches |
| Cross-Case Deduplication | Reuses valid checks from previous verifications of the same entity | Returning entities |
## UBO Discovery
When you submit a company for Company Verification, Zenoo queries the company registry to extract beneficial owners (UBOs). These are individuals with significant control or ownership (typically 25%+ voting rights or shares).
Discovered UBOs are:
* Added to the `beneficial_owners[]` array in the response
* Automatically screened for PEP and sanctions exposure
* Merged with any UBOs you provided in the request (deduplicated by name and date of birth)
If the registry does not disclose UBO information (varies by jurisdiction), the response only includes UBOs you submitted.
## Company Intelligence
For Company Verification flows, Zenoo runs AI-powered research on the company to generate risk insights. This includes:
* Public filings and regulatory actions
* Industry risk classification
* Corporate structure analysis
* Negative news and reputation signals
The results contribute to the `risk_assessment.factors[]` array in the compliance report. Company intelligence runs in the background and does not add latency to the primary registry and screening checks.
## Document Forensics
When a user uploads an identity document during a Person Verification flow, Zenoo performs automated fraud analysis:
* **Digital tampering detection.** Identifies pixel-level manipulation, font inconsistencies, and metadata anomalies.
* **Document authenticity.** Validates security features (holograms, watermarks, MRZ checksums) against known document templates.
* **Image quality assessment.** Detects blurry, cropped, or screen-captured images that may indicate fraud attempts.
Results appear in the `document.tamper_result` and `document.validation_result` fields. A `Warning` or `ATTENTION` result does not necessarily mean fraud, but it flags the document for manual review.
## Risk Model Evaluation
Every verification runs through a multi-dimensional risk model that evaluates four dimensions:
1. **Entity-Attribute Risk.** Entity type, industry classification, incorporation age, revenue profile.
2. **Country Risk.** FATF grey/black list status, Transparency International scores, sanctions program exposure.
3. **Screening Risk.** PEP, sanctions, adverse media, and watchlist matches across all screened entities.
4. **Product/Channel Risk.** Risk profile of the product type and onboarding channel.
Each dimension contributes a weighted score to the overall `risk_assessment.overall_score` (0-100) and determines the `risk_tier` (High, Medium, Low). See [Risk Assessment](/data-model/risk-assessment) for the full field reference.
## Alert Generation
When screening returns matches, Zenoo automatically generates compliance alerts:
* **PEP alerts.** Created when `pep_status` is `Hit` on any entity.
* **Sanctions alerts.** Created when `sanctions_status` is `Hit`. These are flagged as the highest priority.
* **Adverse media alerts.** Created when `adverse_media_status` is `Hit`, with the number of articles noted.
* **Data mismatch alerts.** Created when submitted data conflicts with verified data (e.g., name on document differs from request).
Alerts are surfaced in the Zenoo dashboard for compliance analyst review. They do not block the API response. Your system receives the full screening results and can implement its own alerting logic based on the `screening` section of the compliance report.
## Cross-Case Deduplication
When Zenoo encounters an entity that has been verified before (matched by name, date of birth, and nationality), it checks whether previous verification results are still valid.
Valid checks are reused instead of re-executing. This:
* Reduces verification costs for returning entities
* Speeds up processing time
* Maintains a consistent verification trail across cases
Reused checks are marked in the `checks_summary` with the original case reference. Validity periods vary by check type:
| Check Category | Default Validity |
| - | - |
| Identity | 365 days |
| Screening | 90 days |
| Company | 180 days |
| Financial | 365 days |
| EDD | 180 days |
If a previous check has expired, a fresh check is executed automatically.
# Entities
Source: https://docs.zenoo.com/data-model/entities
Complete field reference for the Entity resource in the Zenoo CLM API.
# Entities
An entity represents a person or company under investigation. Entities are linked to cases via roles (Primary, UBO, Director, Shareholder) and track their verification status independently across cases.
## Resource fields
| Field | Type | Description |
| - | - | - |
| `token` | string | Unique entity identifier (prefix `ent_`) |
| `type` | string | Entity type: `company` or `person` |
| `name` | string | Full name (company name or person full name) |
| `first_name` | string | First name (person entities only) |
| `last_name` | string | Last name (person entities only) |
| `date_of_birth` | string | Date of birth in `YYYY-MM-DD` format (person only) |
| `nationality` | string | ISO 3166-1 alpha-2 nationality code (person only) |
| `registration_number` | string | Company registration number (company only) |
| `country` | string | ISO 3166-1 alpha-2 country code |
| `company_type` | string | Legal entity type (company only) |
| `incorporation_date` | string | Date of incorporation `YYYY-MM-DD` (company only) |
| `industry` | string | Industry classification (company only) |
| `address_line_1` | string | Street address |
| `city` | string | City |
| `postcode` | string | Postal code |
| `email` | string | Email address |
| `phone` | string | Phone number |
| `verification_status` | string | Overall status: `Not Started`, `In Progress`, `Verified`, `Expired`, `Failed` |
| `last_verified_date` | string | ISO 8601 timestamp of last full verification |
| `risk_rating` | string | Entity risk rating: `High`, `Medium`, `Low` |
| `ownership_percentage` | number | Beneficial ownership percentage (UBOs only) |
| `role` | string | Role in case context: `Primary`, `UBO`, `Director`, `Shareholder` |
| `related_entities` | array | Related person entities (directors, UBOs) -- see below |
| `external_reference` | string | Your external entity identifier |
| `created_at` | string | ISO 8601 timestamp of creation |
| `updated_at` | string | ISO 8601 timestamp of last modification |
## Related entity object
When retrieving a company entity, `related_entities` contains linked persons:
| Field | Type | Description |
| - | - | - |
| `token` | string | Person entity token |
| `name` | string | Full name |
| `role` | string | Role: `Director`, `UBO`, `Shareholder` |
| `ownership_percentage` | number | Ownership percentage (UBOs only) |
| `verification_status` | string | Individual verification status |
## Company-to-Person Verification Flow
When you verify a company, Zenoo extracts directors and beneficial owners from the company registry and screens each one for PEP/sanctions/adverse media. If any individual requires further identity verification (document check, biometrics), you initiate a separate Person Verification for that specific person.
Submit the company via `POST /api/gateway/execute/{project_hash}/api` (sync) or `/init` (async). Include any known directors and UBOs in the request.
Zenoo retrieves the company record from the official registry and discovers directors, shareholders, and beneficial owners — including any not provided in your request.
Every individual (submitted and discovered) is automatically screened for PEP, sanctions, and adverse media. Results appear in the `directors[]` and `beneficial_owners[]` arrays of the [compliance report](/data-model/compliance-report).
For individuals who require identity verification (e.g., UBOs with >25% ownership), initiate a Person Verification:
```bash theme={null}
POST /api/gateway/execute/{project_hash}/kyc/init
```
Pass the individual's details and use the returned `start` token to send them a verification link. See [Person Verification Request Fields](/data-model/request-fields-kyc).
Once all individual verifications complete, the company's overall `verification_status` updates and the `identity_verified` flag on each beneficial owner reflects their current state.
You do not need to wait for all Person Verifications to complete before acting on the company results. The company verification verdict and screening results are available immediately. Person Verification results update the individual records independently.
## Company entity example
```json theme={null}
{
"token": "ent_abc123",
"type": "company",
"name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"company_type": "Private Limited Company (Ltd)",
"incorporation_date": "2015-03-15",
"industry": "Financial Services",
"address_line_1": "123 High Street",
"city": "London",
"postcode": "EC1A 1BB",
"verification_status": "Verified",
"last_verified_date": "2026-02-16T11:35:00Z",
"risk_rating": "Medium",
"related_entities": [
{
"token": "ent_dir456",
"name": "Jane Smith",
"role": "Director",
"verification_status": "Verified"
},
{
"token": "ent_ubo789",
"name": "John Doe",
"role": "UBO",
"ownership_percentage": 75,
"verification_status": "Verified"
}
],
"external_reference": "YOUR-ENT-001",
"created_at": "2026-02-16T10:00:00Z",
"updated_at": "2026-02-16T11:35:00Z"
}
```
## Person entity example
```json theme={null}
{
"token": "ent_ubo789",
"type": "person",
"name": "John Doe",
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1975-11-10",
"nationality": "GB",
"country": "GB",
"verification_status": "Verified",
"last_verified_date": "2026-02-16T11:00:00Z",
"risk_rating": "Medium",
"role": "UBO",
"ownership_percentage": 75,
"external_reference": "YOUR-UBO-001",
"created_at": "2026-02-16T10:00:00Z",
"updated_at": "2026-02-16T11:00:00Z"
}
```
## Related endpoints
| Endpoint | Method | Description |
| - | - | - |
| `GET /v1/entities` | GET | List entities with filters |
| `POST /v1/entities` | POST | Create a new entity |
| `GET /v1/entities/{token}` | GET | Get entity details |
| `PATCH /v1/entities/{token}` | PATCH | Update entity fields |
| `GET /v1/entities/{token}/cases` | GET | List cases for an entity |
| `GET /v1/entities/{token}/checks` | GET | List checks for an entity |
| `POST /v1/entities/{token}/ai-research` | POST | Trigger AI research on an entity |
## See also
* [Entity Lifecycle Guide](/guides/entity-lifecycle)
* [Status Enums Reference](/reference/status-enums)
# Individual Verification
Source: https://docs.zenoo.com/data-model/individual-verification
Individual Person Verification results across six domains: identity, document, biometric, phone, email, and address.
# Individual Verification
Individual (Person Verification) results are returned across six domains: identity, document, biometric, phone, email, and address. Each domain has its own section in the [compliance report](/data-model/compliance-report).
## Identity Verification
The `identity` object provides the overall identity verification decision.
| Field | Type | Description |
| - | - | - |
| `verified` | boolean | `true` if identity was successfully verified |
| `decision` | string | `Pass`, `Refer`, or `Fail` |
| `score` | number | Confidence score, 0-100 |
| `authentication_id` | string | Provider reference ID for audit purposes |
### Example
```json theme={null}
{
"identity": {
"verified": true,
"decision": "Pass",
"score": 95,
"authentication_id": "auth-xyz789"
}
}
```
A `score` above 80 typically indicates a strong match. Scores between 60-80 may result in a `Refer` decision, requiring manual review.
## Document Verification
The `document` object contains results from identity document scanning, OCR extraction, and tamper analysis.
| Field | Type | Description |
| - | - | - |
| `verified` | boolean | `true` if the document passed all authenticity checks |
| `document_type` | string | Type of document: `Passport`, `ID Card`, `Driving Licence` |
| `document_country` | string | Issuing country (ISO 3166-1 alpha-2) |
| `validation_result` | string | `PASSED`, `ATTENTION`, or `FAILED` |
| `tamper_result` | string | `Pass`, `Warning`, or `Failed` |
| `extracted_data` | object | OCR-extracted fields from the document |
### Extracted Data Fields
| Field | Type | Description |
| - | - | - |
| `extracted_data.full_name` | string | Full name as printed on the document (typically uppercase) |
| `extracted_data.date_of_birth` | string (date) | Date of birth extracted via OCR |
| `extracted_data.expiration_date` | string (date) | Document expiration date |
| `extracted_data.gender` | string | `M` or `F` |
### Example
```json theme={null}
{
"document": {
"verified": true,
"document_type": "Passport",
"document_country": "GB",
"validation_result": "PASSED",
"tamper_result": "Pass",
"extracted_data": {
"full_name": "JANE ELIZABETH SMITH",
"date_of_birth": "1990-06-15",
"expiration_date": "2030-12-01",
"gender": "F"
}
}
}
```
### Validation Results
| Value | Meaning | Action |
| - | - | - |
| `PASSED` | Document is authentic and legible | Proceed |
| `ATTENTION` | Potential issues detected (blurry image, minor discrepancy) | Manual review recommended |
| `FAILED` | Document failed authenticity checks (tampering, expired, unrecognized) | Reject or request resubmission |
### Tamper Results
| Value | Meaning |
| - | - |
| `Pass` | No signs of digital manipulation |
| `Warning` | Possible tampering detected, requires human review |
| `Failed` | Clear evidence of document tampering |
## Biometric Verification
The `biometric` object contains liveness detection and face matching results.
| Field | Type | Description |
| - | - | - |
| `liveness_verified` | boolean | `true` if the user passed liveness detection |
| `liveness_assessment` | string | `Live` or `Spoof` |
| `liveness_score` | number | Liveness confidence, 0-100 |
| `face_match` | boolean | `true` if the selfie matches the document photo |
| `face_match_score` | number | Face match confidence, 0-100 |
### Example
```json theme={null}
{
"biometric": {
"liveness_verified": true,
"liveness_assessment": "Live",
"liveness_score": 98.5,
"face_match": true,
"face_match_score": 96.2
}
}
```
A `liveness_score` below 70 or a `face_match_score` below 75 typically triggers a `Refer` decision. A `Spoof` assessment means the system detected a photo-of-photo, screen recording, or mask attempt.
## Phone Verification
The `phone_verification` object contains results from phone number validation.
| Field | Type | Description |
| - | - | - |
| `verified` | boolean | `true` if the phone number was successfully verified |
| `carrier` | string | Mobile carrier name (e.g., `Vodafone UK`) |
| `phone_type` | string | `mobile`, `landline`, or `voip` |
### Example
```json theme={null}
{
"phone_verification": {
"verified": true,
"carrier": "Vodafone UK",
"phone_type": "mobile"
}
}
```
Phone verification runs when a `phone` field is provided in the request. VoIP numbers may indicate higher risk depending on your compliance policy.
## Email Verification
The `email_verification` object contains results from email address analysis.
| Field | Type | Description |
| - | - | - |
| `verified` | boolean | `true` if the email address is valid and deliverable |
| `risk_score` | number | Fraud risk score, 0-100. Lower is better |
| `deliverable` | boolean | `true` if the email can receive messages |
### Example
```json theme={null}
{
"email_verification": {
"verified": true,
"risk_score": 12,
"deliverable": true
}
}
```
A `risk_score` above 70 suggests the email may be disposable, recently created, or associated with known fraud patterns.
## Address Verification
The `address_verification` object contains results from address validation.
| Field | Type | Description |
| - | - | - |
| `verified` | boolean | `true` if the address could be validated |
| `match_level` | string | `full`, `partial`, or `none` |
### Example
```json theme={null}
{
"address_verification": {
"verified": true,
"match_level": "full"
}
}
```
### Match Levels
| Value | Meaning |
| - | - |
| `full` | Complete address match against reference databases |
| `partial` | Street or city matched, but some components (e.g., apartment number) could not be confirmed |
| `none` | Address could not be verified against any reference source |
## Decision Matrix
Combine results across domains to determine your action:
| Identity | Document | Biometric | Screening | Recommended Action |
| - | - | - | - | - |
| Pass | Pass | Pass | No Hit | Auto-approve |
| Pass | Attention | Pass | No Hit | Manual document review |
| Pass | Pass | Pass | Hit | Manual screening review |
| Fail | Any | Any | Any | Reject or request resubmission |
Only auto-approve when all four domains return clean results. Any `Attention`, `Warning`, or `Hit` flag should trigger manual review.
# Data Model
Source: https://docs.zenoo.com/data-model/overview
Zenoo accepts and returns verification data at three tiers: Minimum, Standard, and Full.
Zenoo accepts and returns verification data at three tiers: **Minimum**, **Standard**, and **Full**. The more data you provide, the richer the results, the faster the processing, and the fewer false positives.
## Three-Tier Data Model
Every verification request accepts a flexible set of fields. Not all fields are required. Zenoo categorizes input into three tiers based on how much data you supply:
| Tier | What You Provide | What You Get |
| - | - | - |
| **Minimum** | Core identifiers only | Basic verification and screening |
| **Standard** | Identifiers + context (addresses, dates, related persons) | Full verification with entity correlation and risk scoring |
| **Full** | Everything available, including external references and financial data | Maximum accuracy, idempotency, enriched risk assessment |
Providing Standard tier or above is recommended for production deployments.
## Company Verification Tiers
| Tier | Required Fields | Optional Additions |
| - | - | - |
| **Minimum** | `company_name`, `registration_number`, `country` | Registry check + company screening |
| **Standard** | + `directors[]`, `ubos[]`, `address`, `industry` | Individual screening, entity correlation |
| **Full** | + `external_reference`, `annual_revenue`, `sic_codes`, `employee_count` | Idempotency, enriched risk scoring |
See [Company Verification Request Fields](/data-model/request-fields-kyb) for the complete field reference.
## Person Verification Tiers
| Tier | Required Fields | Optional Additions |
| - | - | - |
| **Minimum** | `first_name`, `last_name`, `country` | Basic screening |
| **Standard** | + `date_of_birth`, `email`, `phone`, `address`, `city`, `postal_code` | Identity verification, phone/email checks |
| **Full** | + `nationality`, `document_type`, `external_reference` | Idempotency, document preference, enriched risk scoring |
See [Person Verification Request Fields](/data-model/request-fields-kyc) for the complete field reference.
## Response Structure
Zenoo returns structured compliance reports with results grouped by verification domain:
Top-level report with verdict, risk tier, and metadata
Registry data, directors, beneficial owners
Identity, document, biometric, phone, email, address
PEP, sanctions, adverse media, watchlist matches
Multi-dimensional risk scoring and check summary
## Additional References
Standalone screening input fields
Automatic data enrichments applied to every verification
Complete registry of verification check codes and methods
# Company Verification Request Fields
Source: https://docs.zenoo.com/data-model/request-fields-kyb
Submit company data for Company Verification with three tiers of input fields.
# Company Verification Request Fields
Submit company data for Company Verification. Zenoo accepts three tiers of data. More data produces richer results and fewer false positives.
## Tier Examples
### Tier 1: Minimum
The bare minimum for a Company Verification. Returns registry check and company screening.
```json theme={null}
{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB"
}
```
### Tier 2: Standard
Adds directors, UBOs, and address data. Triggers individual screening for each person and enables entity correlation.
```json theme={null}
{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"company_type": "Private Limited Company (Ltd)",
"incorporation_date": "2015-03-15",
"industry": "Financial Services",
"address_line_1": "123 High Street",
"city": "London",
"postcode": "EC1A 1BB",
"directors": [
{
"first_name": "Jane",
"last_name": "Smith",
"date_of_birth": "1980-05-20",
"nationality": "GB",
"role": "Director"
}
],
"ubos": [
{
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1975-11-10",
"nationality": "GB",
"ownership_percentage": 75,
"role": "UBO"
}
]
}
```
### Tier 3: Full
Maximum data for the highest accuracy. Adds financial data, SIC codes, and an external reference for idempotency.
```json theme={null}
{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"company_type": "Private Limited Company (Ltd)",
"incorporation_date": "2015-03-15",
"industry": "Financial Services",
"address_line_1": "123 High Street",
"city": "London",
"postcode": "EC1A 1BB",
"annual_revenue": "5000000",
"employee_count": "42",
"sic_codes": "64110,64191",
"status": "Active",
"external_reference": "ONBOARD-2026-0042",
"directors": [
{
"first_name": "Jane",
"last_name": "Smith",
"date_of_birth": "1980-05-20",
"nationality": "GB",
"role": "Director",
"appointed_date": "2015-03-15",
"zenoo_entity_id": "ENT-DIR-001"
}
],
"ubos": [
{
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1975-11-10",
"nationality": "GB",
"ownership_percentage": 75,
"role": "UBO",
"zenoo_entity_id": "ENT-UBO-001"
}
]
}
```
## Company Fields
The three required fields (`company_name`, `registration_number`, `country`) are sufficient to run a Company Verification. All other fields are optional but improve result accuracy.
| Field | Type | Required | Tier | Description |
| - | - | - | - | - |
| `company_name` | string | Yes | Minimum | Legal name as registered with the company registry |
| `registration_number` | string | Yes | Minimum | Official company registration number |
| `country` | string | Yes | Minimum | Country of incorporation. ISO 3166-1 alpha-2 code (e.g., `GB`, `US`, `DE`) |
| `company_type` | string | No | Standard | Legal entity type (e.g., `Private Limited Company (Ltd)`, `PLC`, `LLP`) |
| `incorporation_date` | string | No | Standard | Date of incorporation. ISO 8601 format: `YYYY-MM-DD` |
| `industry` | string | No | Standard | Industry sector or classification |
| `address_line_1` | string | No | Standard | Registered address street |
| `city` | string | No | Standard | Registered address city |
| `postcode` | string | No | Standard | Postal code |
| `annual_revenue` | string | No | Full | Annual revenue in base currency. Pass as a string (e.g., `"5000000"`) |
| `employee_count` | string | No | Full | Number of employees. Pass as a string (e.g., `"42"`) |
| `sic_codes` | string | No | Full | SIC codes, comma-separated (e.g., `"64110,64191"`) |
| `status` | string | No | Full | Current company status: `Active`, `Dissolved`, `Struck Off` |
| `external_reference` | string | No | Full | Your internal reference ID. Enables idempotency and correlation |
| `directors[]` | array | No | Standard | Known directors. See Director fields below |
| `ubos[]` | array | No | Standard | Known beneficial owners. See UBO fields below |
## Director Fields
Each object in the `directors[]` array accepts:
| Field | Type | Description |
| - | - | - |
| `first_name` | string | Director's first name |
| `last_name` | string | Director's last name |
| `date_of_birth` | string | ISO 8601 date (`YYYY-MM-DD`). Strongly recommended for screening accuracy |
| `nationality` | string | ISO 3166-1 alpha-2 country code |
| `role` | string | Role within the company: `Director`, `Secretary`, `Chair` |
| `appointed_date` | string | Date of appointment. ISO 8601 format |
| `zenoo_entity_id` | string | Your entity reference for correlation across verifications |
## UBO Fields
Each object in the `ubos[]` array accepts:
| Field | Type | Description |
| - | - | - |
| `first_name` | string | Beneficial owner's first name |
| `last_name` | string | Beneficial owner's last name |
| `date_of_birth` | string | ISO 8601 date (`YYYY-MM-DD`). Strongly recommended for screening accuracy |
| `nationality` | string | ISO 3166-1 alpha-2 country code |
| `ownership_percentage` | number | Ownership stake as a whole number, 0-100 (e.g., `75` for 75%) |
| `role` | string | Role: `UBO`, `Shareholder` |
| `zenoo_entity_id` | string | Your entity reference for correlation across verifications |
## Formatting Notes
* **Country codes.** Always use ISO 3166-1 alpha-2 codes: `GB` (not `UK`), `US`, `DE`, `FR`, `IE`, `NL`, `CH`.
* **Dates.** Use ISO 8601 format: `YYYY-MM-DD`. Example: `2015-03-15`.
* **Numeric strings.** `annual_revenue` and `employee_count` are passed as strings, not numbers. This avoids floating-point precision issues with large values.
**External references.** If you submit the same `external_reference` twice, Zenoo returns the existing case tokens without creating a duplicate. Always use external references in production for idempotency.
**Director and UBO screening.** Including `directors[]` and `ubos[]` in your request automatically triggers individual PEP/sanctions screening for each person. If you omit these arrays, only the company entity is screened. Zenoo may also discover additional directors and UBOs from the company registry, and screen those as well.
# Person Verification Request Fields
Source: https://docs.zenoo.com/data-model/request-fields-kyc
Submit individual data for Person Verification with three tiers of input fields.
# Person Verification Request Fields
Submit individual data for Person Verification. Zenoo accepts three tiers of data. More data improves screening accuracy and reduces false positives.
## Tier Examples
### Tier 1: Minimum
The bare minimum for a Person Verification. Returns basic screening results.
```json theme={null}
{
"first_name": "Jane",
"last_name": "Smith",
"country": "GB"
}
```
### Tier 2: Standard
Adds date of birth, contact details, and address. Enables identity verification, phone/email checks, and accurate screening with far fewer false positives.
```json theme={null}
{
"first_name": "Jane",
"last_name": "Smith",
"country": "GB",
"date_of_birth": "1990-06-15",
"email": "jane.smith@example.com",
"phone": "+447700900123",
"address": "42 Baker Street",
"city": "London",
"postal_code": "NW1 6XE"
}
```
### Tier 3: Full
Maximum data for highest accuracy. Adds nationality, document preference, and an external reference for idempotency.
```json theme={null}
{
"first_name": "Jane",
"last_name": "Smith",
"country": "GB",
"date_of_birth": "1990-06-15",
"email": "jane.smith@example.com",
"phone": "+447700900123",
"address": "42 Baker Street",
"city": "London",
"postal_code": "NW1 6XE",
"nationality": "GB",
"document_type": "passport",
"external_reference": "APP-2026-0123"
}
```
## Field Reference
| Field | Type | Required | Tier | Description |
| - | - | - | - | - |
| `first_name` | string | Yes | Minimum | Individual's first name |
| `last_name` | string | Yes | Minimum | Individual's last name |
| `country` | string | Yes | Minimum | Country of residence. ISO 3166-1 alpha-2 code (e.g., `GB`, `US`) |
| `date_of_birth` | string | No | Standard | Date of birth. ISO 8601 format: `YYYY-MM-DD`. Strongly recommended for screening accuracy |
| `email` | string | No | Standard | Email address. Triggers email verification check |
| `phone` | string | No | Standard | Phone number in E.164 format (e.g., `+447700900123`). Triggers phone verification |
| `address` | string | No | Standard | Street address |
| `city` | string | No | Standard | City |
| `postal_code` | string | No | Standard | Postal or ZIP code |
| `nationality` | string | No | Full | Nationality. ISO 3166-1 alpha-2 code |
| `document_type` | string | No | Full | Preferred document type for verification. See accepted values below |
| `external_reference` | string | No | Full | Your internal reference ID. Enables idempotency and correlation |
## Document Types
When you specify `document_type`, the verification UI prompts the user with that document type first. The user can still select a different type if needed.
| Value | Description |
| - | - |
| `passport` | National passport |
| `national_id` | Government-issued national ID card |
| `driving_licence` | Driving licence with photo |
If `document_type` is omitted, the user chooses from all available options.
## Formatting Notes
* **Phone numbers.** Use E.164 format: a `+` followed by the country code and number with no spaces or dashes. Examples: `+447700900123` (UK), `+12025551234` (US), `+353861234567` (IE).
* **Country codes.** Always use ISO 3166-1 alpha-2 codes: `GB` (not `UK`), `US`, `DE`, `FR`.
* **Dates.** Use ISO 8601 format: `YYYY-MM-DD`. Example: `1990-06-15`.
**Date of birth impact.** For screening, `date_of_birth` reduces false positives by 80-95% on common names. If you only provide one optional field, make it the date of birth.
**External references.** If you submit the same `external_reference` twice, Zenoo returns the existing case tokens without creating a duplicate. Always use external references in production for idempotency.
## Sync vs Async
Person Verification supports two execution models:
**Async (user-facing).** Use the `/init` endpoint for flows that require document capture and biometric verification. The user interacts with a verification UI.
```bash theme={null}
POST /api/gateway/execute/{project_hash}/kyc/init
```
**Sync (server-side).** Use the `/api` endpoint with the `X-SYNC-TIMEOUT` header for database lookups, phone verification, and screening when you already have the user's data.
```bash theme={null}
POST /api/gateway/execute/{project_hash}/kyc/api
X-SYNC-TIMEOUT: 30000
```
See [Sync vs Async Flows](/guides/sync-vs-async) for details on choosing the right model.
# Screening Request Fields
Source: https://docs.zenoo.com/data-model/request-fields-screening
Submit an entity for standalone AML screening against PEP, sanctions, adverse media, and watchlist databases.
# Screening Request Fields
Submit an entity for standalone AML screening against PEP, sanctions, adverse media, and watchlist databases. Screening also runs automatically as part of Company Verification and Person Verification flows.
## Request Example
### Screen a Person
```json theme={null}
{
"name": "John Smith",
"date_of_birth": "1975-03-20",
"country": "GB",
"entity_type": "person",
"categories": ["pep", "sanctions", "adverse_media", "watchlist"]
}
```
### Screen a Company
```json theme={null}
{
"name": "Acme Holdings Ltd",
"country": "GB",
"entity_type": "company",
"categories": ["sanctions", "adverse_media"]
}
```
## Field Reference
| Field | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | Full name of the person or legal name of the company |
| `date_of_birth` | string | No | ISO 8601 date (`YYYY-MM-DD`). Person screening only. Reduces false positives by 80-95% on common names |
| `country` | string | No | ISO 3166-1 alpha-2 code (e.g., `GB`). Filters results by jurisdiction |
| `entity_type` | string | No | `person` or `company`. Defaults to `person` if omitted |
| `categories[]` | array | No | Screening categories to check. Defaults to all categories if omitted |
## Screening Categories
| Value | Description | Typical Sources |
| - | - | - |
| `pep` | Politically Exposed Persons | Government records, parliamentary registers |
| `sanctions` | Global sanctions lists | OFAC SDN, EU, UN, UK HMT |
| `adverse_media` | Negative news and media | News databases, financial crime reporting |
| `watchlist` | Law enforcement and regulatory watchlists | Interpol, FBI, national fugitive lists |
If you omit the `categories[]` array, Zenoo screens against all four categories.
## Person vs Company Screening
Person and company screening use different matching algorithms optimized for each entity type.
**Person screening** matches on name, date of birth, nationality, and aliases. Providing `date_of_birth` is the single most effective way to reduce false positives. Without it, common names like "John Smith" can match hundreds of records.
**Company screening** matches on legal name, registration jurisdiction, and known aliases. The `date_of_birth` field is ignored for company screening. Companies are typically screened against `sanctions` and `adverse_media` categories only, since PEP status applies to individuals.
## When to Use Standalone Screening
Screening runs automatically inside Company Verification and Person Verification flows. Use the standalone screening endpoint for:
* **Ongoing monitoring.** Re-screen existing customers on a schedule.
* **Quick pre-checks.** Screen a name before starting full verification.
* **Batch screening.** Screen multiple entities without running full verification.
* **Custom workflows.** Screen entities that are not part of a standard verification flow.
# Risk Assessment
Source: https://docs.zenoo.com/data-model/risk-assessment
Quantified risk evaluation with multi-dimensional scoring and a summary of every verification check performed.
# Risk Assessment
The `risk_assessment` and `checks_summary` objects appear in the [compliance report](/data-model/compliance-report) for both Company Verification and Person Verification flows. Together they provide a quantified risk evaluation and a summary of every check that was performed.
## Risk Assessment Object
| Field | Type | Description |
| - | - | - |
| `overall_score` | number | Numeric risk score, 0-100. Higher means riskier |
| `tier` | string | `High`, `Medium`, or `Low` |
| `factors[]` | array | Breakdown of individual risk factors |
### Example
```json theme={null}
{
"risk_assessment": {
"overall_score": 45,
"tier": "Medium",
"factors": [
{
"factor": "Adverse Media",
"severity": "Medium",
"detail": "2 adverse media articles found for company"
},
{
"factor": "Jurisdiction",
"severity": "Low",
"detail": "Country of incorporation is low-risk (GB)"
}
]
}
}
```
## Risk Score Ranges
| Score | Tier | Meaning |
| - | - | - |
| 0-30 | Low | Low risk. Standard monitoring sufficient |
| 31-60 | Medium | Moderate risk. Review recommended |
| 61-100 | High | Elevated risk. Enhanced due diligence required |
The score is computed from multiple dimensions. A single high-severity factor can push the score into the High tier even if other dimensions are clean.
## Factor Object
Each entry in the `factors[]` array describes one risk dimension that contributed to the overall score.
| Field | Type | Description |
| - | - | - |
| `factor` | string | Name of the risk factor |
| `severity` | string | `High`, `Medium`, or `Low` |
| `detail` | string | Human-readable explanation |
### Common Factor Names
| Factor | What It Evaluates |
| - | - |
| `Entity Attribute` | Entity-level characteristics: entity type, industry, revenue, age of incorporation |
| `Country Risk` | Jurisdiction risk based on FATF grey/black lists, Transparency International CPI, sanctions exposure |
| `Screening Risk` | PEP, sanctions, adverse media, and watchlist hits across all screened entities |
| `Product/Channel Risk` | Risk associated with the product type and onboarding channel |
| `Adverse Media` | Volume and severity of negative media coverage |
| `Jurisdiction` | Country of incorporation or residence risk classification |
| `Ownership Structure` | Complexity of beneficial ownership (multiple layers, nominee shareholders) |
| `Data Mismatch` | Discrepancies between submitted data and verified data (name, DOB, address) |
## Risk Dimensions
The overall score is derived from four core dimensions, each evaluated independently and weighted:
**Entity-Attribute Risk.** Characteristics of the entity itself. High-risk industries (gambling, crypto, arms), newly incorporated companies, and unusually high revenue for the entity size all increase this score.
**Country Risk.** Based on the jurisdiction of incorporation, operations, and nationality of related persons. Countries on the FATF grey list, those with low Transparency International scores, or those under broad sanctions programs contribute higher risk.
**Screening Risk.** Derived from PEP, sanctions, adverse media, and watchlist screening results. A single sanctions hit produces the maximum score in this dimension. Multiple PEP matches or high-volume adverse media increase the score proportionally.
**Product/Channel Risk.** Assesses the risk profile of the product being onboarded for and the channel through which the application was submitted. Remote onboarding, high-value products, and cross-border arrangements carry higher risk.
## Checks Summary
The `checks_summary` object provides an aggregate view of all verification checks performed.
| Field | Type | Description |
| - | - | - |
| `total` | integer | Total number of checks performed |
| `passed` | integer | Checks that passed |
| `referred` | integer | Checks that require manual review |
| `failed` | integer | Checks that failed |
| `checks[]` | array | Per-check detail |
### Check Object
| Field | Type | Description |
| - | - | - |
| `type` | string | Check type name (e.g., `Company Registration`, `PEP/Sanctions Screening`) |
| `status` | string | `Pass`, `Refer`, or `Fail` |
| `provider` | string | Provider that executed the check (e.g., `Registry`, `WorldCheck`, `Zenoo`) |
| `matches` | integer | Number of matches found (screening checks only) |
| `completed_at` | string (datetime) | ISO 8601 timestamp of check completion |
### Example
```json theme={null}
{
"checks_summary": {
"total": 5,
"passed": 4,
"referred": 1,
"failed": 0,
"checks": [
{
"type": "Company Registration",
"status": "Pass",
"provider": "Registry",
"completed_at": "2026-01-15T14:30:15Z"
},
{
"type": "Company Screening",
"status": "Refer",
"provider": "WorldCheck",
"matches": 2,
"completed_at": "2026-01-15T14:30:20Z"
},
{
"type": "Director Screening (Jane Smith)",
"status": "Pass",
"provider": "WorldCheck",
"completed_at": "2026-01-15T14:30:25Z"
},
{
"type": "UBO Screening (John Doe)",
"status": "Pass",
"provider": "WorldCheck",
"completed_at": "2026-01-15T14:30:26Z"
},
{
"type": "Directors Verification",
"status": "Pass",
"provider": "Registry",
"completed_at": "2026-01-15T14:30:28Z"
}
]
}
}
```
## Using Risk Data in Your System
### Automated Decisioning
Map the `tier` field to your enrichment pipeline's decision logic:
```
if risk_tier == "Low" and overall_verdict == "Pass":
auto_approve()
elif risk_tier == "Medium":
queue_for_review()
elif risk_tier == "High":
queue_for_senior_review()
flag_for_edd()
```
### Risk Factor Monitoring
Store the `factors[]` array alongside the case. Over time, patterns in factor frequency and severity reveal systemic risks in your customer base. For example, if `Country Risk` factors are consistently flagged, you may need to adjust your geographic risk appetite.
### Check Failure Analysis
A `Fail` status on any check means that specific verification could not be completed or returned a negative result. Common causes:
* **Registry check failed.** Company not found in the registry, or registry returned dissolved/struck-off status.
* **Screening referred.** Matches found that require human review.
* **Document check failed.** Tamper detection triggered, expired document, or unreadable image.
* **Identity check failed.** Face match below threshold, liveness check failed, or database lookup returned no match.
Check the `provider` field to understand which upstream service produced the result.
# Screening Results
Source: https://docs.zenoo.com/data-model/screening-results
Screening results including match details for PEP, sanctions, adverse media, and watchlist hits.
# Screening Results
Screening results appear in the `screening` section of the [compliance report](/data-model/compliance-report) for both Company Verification and Person Verification flows, and as the primary response for standalone screening requests.
## Screening Summary
The top-level screening object provides a quick status overview.
| Field | Type | Description |
| - | - | - |
| `pep_status` | string | `No Hit` or `Hit` |
| `sanctions_status` | string | `No Hit` or `Hit` |
| `adverse_media_status` | string | `No Hit` or `Hit` |
| `adverse_media_count` | integer | Number of adverse media matches (0 if no hits) |
| `screening_provider` | string | Provider name (e.g., `WorldCheck`) |
| `screening_completed_at` | string (datetime) | ISO 8601 timestamp of screening completion |
### Example: Clean Result
```json theme={null}
{
"screening": {
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "No Hit",
"screening_provider": "WorldCheck",
"screening_completed_at": "2026-01-15T14:30:00Z"
}
}
```
### Example: Result with Hits
```json theme={null}
{
"screening": {
"pep_status": "Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "Hit",
"adverse_media_count": 2,
"screening_provider": "WorldCheck",
"screening_completed_at": "2026-01-15T14:30:00Z"
}
}
```
## Match Details
When hits are found, the `matches[]` array contains detailed information about each match.
| Field | Type | Description |
| - | - | - |
| `matchScore` | number | Match confidence score, 0-100 |
| `primaryName` | string | Primary name of the matched entity in the database |
| `category` | string | Primary match category: `pep`, `sanctions`, `adverse_media`, `watchlist` |
| `categories[]` | array of string | All categories for this match (e.g., `pep-class-2`, `adverse-media-financial-crime`) |
| `types[]` | array of string | Specific sub-types (e.g., `sanction-sdn`, `pep-class-2`) |
| `nationality` | string | Nationality of the matched entity |
| `dateOfBirth` | string | Date of birth of the matched entity |
| `sources[]` | array of string | Data sources (e.g., `OFAC SDN`, `UK HMT`, `UK Parliament`) |
| `media[]` | array of object | Associated news articles (adverse media matches) |
| `associates[]` | array of object | Known associates (PEP matches) |
| `positions[]` | array of object | Political positions held (PEP matches) |
### Media Object
| Field | Type | Description |
| - | - | - |
| `media[].title` | string | Article headline |
| `media[].url` | string | Article URL |
| `media[].snippet` | string | Brief excerpt from the article |
| `media[].date` | string | Publication date |
### Associate Object
| Field | Type | Description |
| - | - | - |
| `associates[].name` | string | Associate's full name |
| `associates[].association` | string | Relationship description (e.g., `Spouse`, `Business Partner`) |
| `associates[].type` | string | Association category: `family` or `business` |
### Position Object
| Field | Type | Description |
| - | - | - |
| `positions[].title` | string | Position title (e.g., `Member of Parliament`) |
| `positions[].country` | string | Country of the position |
| `positions[].from` | string | Start date |
| `positions[].to` | string | End date (empty if current) |
### Full Match Example
```json theme={null}
{
"matches": [
{
"matchScore": 95.0,
"primaryName": "John Andrew Smith",
"category": "pep",
"categories": ["pep-class-2"],
"types": ["pep-class-2"],
"nationality": "British",
"dateOfBirth": "1975-03-20",
"sources": ["UK Parliament"],
"positions": [
{
"title": "Member of Parliament",
"country": "United Kingdom",
"from": "2019-12-12",
"to": "2024-07-04"
}
],
"associates": [
{
"name": "Mary Smith",
"association": "Spouse",
"type": "family"
}
]
},
{
"matchScore": 78.5,
"primaryName": "John Smith",
"category": "adverse_media",
"categories": ["adverse-media-financial-crime"],
"types": ["adverse-media"],
"nationality": "British",
"media": [
{
"title": "Investigation into financial irregularities at Acme Holdings",
"url": "https://example.com/article-123",
"snippet": "Authorities are investigating potential financial irregularities...",
"date": "2025-06-15"
}
]
}
]
}
```
## Match Score Interpretation
| Score Range | Confidence | Recommended Action |
| - | - | - |
| 90-100 | Very High | Strong match. Investigate immediately |
| 80-89 | High | Likely match. Manual review required |
| 70-79 | Medium | Possible match. Review recommended |
| Below 70 | Low | Weak match. Likely a false positive |
Scores above 90 combined with matching date of birth and nationality are near-certain matches. Scores in the 70-79 range should still be reviewed, especially when combined with other risk factors like high-risk jurisdictions or complex ownership structures.
## PEP Classifications
WorldCheck classifies Politically Exposed Persons into four tiers:
| Class | Description | Examples |
| - | - | - |
| `pep-class-1` | Heads of state and senior national government | Presidents, prime ministers, cabinet members |
| `pep-class-2` | Regional and local government officials | Members of parliament, mayors, governors |
| `pep-class-3` | Other senior public officials | Senior military officers, judiciary, central bank governors |
| `pep-class-4` | Associates and family members | Spouses, children, known business partners of PEP classes 1-3 |
PEP Class 1 matches require senior compliance review regardless of match score. Classes 2-3 require standard enhanced due diligence. Class 4 matches indicate close association with a PEP and should be flagged for review.
## Sanctions List Types
| Type | Source | Jurisdiction |
| - | - | - |
| `sanction-sdn` | OFAC Specially Designated Nationals | United States |
| `sanction-eu` | EU Consolidated Sanctions List | European Union |
| `sanction-un` | UN Security Council Consolidated List | International |
| `sanction-hmt` | HM Treasury Consolidated List | United Kingdom |
Any sanctions match, regardless of score or list, requires immediate action. Do not proceed with onboarding or transaction processing for sanctioned entities.
## Screening Decision Matrix
| Category | Score | Recommended Action |
| - | - | - |
| Sanctions Hit | Any | Immediate escalation. Do not proceed |
| PEP Class 1-2 | Above 80 | Enhanced due diligence required |
| PEP Class 3-4 | Above 80 | Standard review with ongoing monitoring |
| Adverse Media | Above 85 | Manual review required |
| Adverse Media | 70-85 | Review if combined with other risk factors |
| Watchlist | Any | Immediate escalation |
# Webhook Events
Source: https://docs.zenoo.com/data-model/webhook-events
CLM webhook event types with payload structures for real-time case, alert, check, and risk assessment notifications.
# Webhook Events
Zenoo sends webhook events to your configured endpoint when significant actions occur in the CLM platform. Use webhooks to build real-time integrations, trigger workflows, and keep external systems in sync.
## Event delivery
| Property | Value |
| - | - |
| Method | `POST` |
| Content-Type | `application/json` |
| Signature header | `X-Zenoo-Signature` (HMAC-SHA256) |
| Retry schedule | 1 min, 5 min, 30 min, 2 hr, 24 hr |
| Idempotency | Each event has a unique `event_id` |
| Timeout | 30 seconds (your endpoint must respond) |
Always verify the `X-Zenoo-Signature` header before processing webhook payloads. See the signature verification section below.
## Event types
### Case events
| Event | Trigger |
| - | - |
| `case.created` | New case created |
| `case.status_changed` | Case status transition |
| `case.closed` | Case closed with resolution |
| `case.escalated` | Case escalated to a manager |
### Alert events
| Event | Trigger |
| - | - |
| `alert.created` | New alert generated from check result |
| `alert.resolved` | Alert resolved by analyst or auto-disposition |
| `alert.escalated` | Alert escalated to manager |
| `alert.ai_research_completed` | AI research completed for alert |
### Check events
| Event | Trigger |
| - | - |
| `check.completed` | Check execution completed with result |
| `check.failed` | Check execution failed |
### Risk events
| Event | Trigger |
| - | - |
| `risk_assessment.completed` | Risk assessment scoring completed |
| `risk_assessment.approved` | Risk assessment approved by reviewer |
### SLA events
| Event | Trigger |
| - | - |
| `sla.warning` | Case approaching SLA deadline (warning threshold) |
| `sla.breached` | Case has passed its SLA deadline |
## Common envelope
Every webhook event uses this envelope structure:
```json theme={null}
{
"event_id": "evt_abc123xyz",
"event_type": "case.created",
"created_at": "2026-02-16T10:01:00Z",
"data": {
// Event-specific payload
}
}
```
| Field | Type | Description |
| - | - | - |
| `event_id` | string | Unique event identifier for idempotency |
| `event_type` | string | Event type string |
| `created_at` | string | ISO 8601 timestamp of event creation |
| `data` | object | Event-specific payload (varies by type) |
## Event payloads
### case.created
```json theme={null}
{
"event_id": "evt_001",
"event_type": "case.created",
"created_at": "2026-02-16T10:01:00Z",
"data": {
"case_token": "cas_xyz789",
"type": "Onboarding",
"status": "New",
"priority": "Medium",
"entity_token": "ent_abc123",
"entity_name": "Acme Holdings Ltd",
"due_date": "2026-03-02T10:00:00Z"
}
}
```
### case.status\_changed
```json theme={null}
{
"event_id": "evt_002",
"event_type": "case.status_changed",
"created_at": "2026-02-16T10:30:00Z",
"data": {
"case_token": "cas_xyz789",
"old_status": "New",
"new_status": "In Progress",
"changed_by": { "id": "user_analyst01", "name": "Sarah Johnson" }
}
}
```
### case.closed
```json theme={null}
{
"event_id": "evt_003",
"event_type": "case.closed",
"created_at": "2026-02-16T11:35:00Z",
"data": {
"case_token": "cas_xyz789",
"risk_tier": "Medium",
"risk_score": 45,
"resolution_notes": "All checks passed. PEP match reviewed and cleared.",
"closed_by": { "id": "user_analyst01", "name": "Sarah Johnson" }
}
}
```
### case.escalated
```json theme={null}
{
"event_id": "evt_004",
"event_type": "case.escalated",
"created_at": "2026-02-16T12:00:00Z",
"data": {
"case_token": "cas_xyz789",
"escalated_by": { "id": "user_analyst01", "name": "Sarah Johnson" },
"escalated_to": { "id": "user_manager01", "name": "Mike Chen" },
"reason": "Complex corporate structure requires senior review."
}
}
```
### alert.created
```json theme={null}
{
"event_id": "evt_005",
"event_type": "alert.created",
"created_at": "2026-02-16T10:15:00Z",
"data": {
"alert_token": "alt_001",
"case_token": "cas_xyz789",
"type": "Screening",
"category": "PEP Match",
"title": "PEP Match: John Doe",
"priority": "High",
"priority_score": 78,
"entity_name": "John Doe"
}
}
```
### alert.resolved
```json theme={null}
{
"event_id": "evt_006",
"event_type": "alert.resolved",
"created_at": "2026-02-16T11:30:00Z",
"data": {
"alert_token": "alt_001",
"case_token": "cas_xyz789",
"status": "Resolved",
"resolution_action": "Approve",
"resolution_notes": "Former local councillor. Low risk.",
"resolved_by": { "id": "user_analyst01", "name": "Sarah Johnson" }
}
}
```
### alert.escalated
```json theme={null}
{
"event_id": "evt_007",
"event_type": "alert.escalated",
"created_at": "2026-02-16T12:00:00Z",
"data": {
"alert_token": "alt_002",
"case_token": "cas_xyz789",
"escalated_by": { "id": "user_analyst01", "name": "Sarah Johnson" },
"escalated_to": { "id": "user_manager01", "name": "Mike Chen" },
"reason": "Potential sanctions match requires senior review."
}
}
```
### alert.ai\_research\_completed
```json theme={null}
{
"event_id": "evt_008",
"event_type": "alert.ai_research_completed",
"created_at": "2026-02-16T10:05:30Z",
"data": {
"alert_token": "alt_001",
"confidence": 85,
"false_positive_probability": 82,
"recommended_action": "Approve"
}
}
```
### check.completed
```json theme={null}
{
"event_id": "evt_009",
"event_type": "check.completed",
"created_at": "2026-02-16T10:10:12Z",
"data": {
"check_token": "chk_001",
"case_token": "cas_xyz789",
"type": "PEP Screening",
"category": "Screening",
"result": "Refer",
"provider": "WorldCheck",
"entity_name": "John Doe",
"alert_generated": true,
"alert_token": "alt_001"
}
}
```
### check.failed
```json theme={null}
{
"event_id": "evt_010",
"event_type": "check.failed",
"created_at": "2026-02-16T10:12:00Z",
"data": {
"check_token": "chk_003",
"case_token": "cas_xyz789",
"type": "Company Registry",
"category": "Company",
"error": "Provider timeout after 30 seconds",
"retry_scheduled": true,
"retry_count": 1
}
}
```
### risk\_assessment.completed
```json theme={null}
{
"event_id": "evt_011",
"event_type": "risk_assessment.completed",
"created_at": "2026-02-16T11:00:00Z",
"data": {
"assessment_token": "rsk_001",
"case_token": "cas_xyz789",
"overall_tier": "Medium",
"overall_score": 45,
"customer_risk": { "score": 40, "tier": "Medium" },
"geographic_risk": { "score": 30, "tier": "Low" },
"product_risk": { "score": 55, "tier": "Medium" },
"channel_risk": { "score": 25, "tier": "Low" },
"edd_required": false
}
}
```
### risk\_assessment.approved
```json theme={null}
{
"event_id": "evt_012",
"event_type": "risk_assessment.approved",
"created_at": "2026-02-16T11:15:00Z",
"data": {
"assessment_token": "rsk_001",
"case_token": "cas_xyz789",
"approved_by": { "id": "user_manager01", "name": "Mike Chen" },
"overall_tier": "Medium"
}
}
```
### sla.warning
```json theme={null}
{
"event_id": "evt_013",
"event_type": "sla.warning",
"created_at": "2026-02-28T01:00:00Z",
"data": {
"case_token": "cas_xyz789",
"due_date": "2026-03-02T10:00:00Z",
"days_remaining": 2,
"assigned_to": { "id": "user_analyst01", "name": "Sarah Johnson" }
}
}
```
### sla.breached
```json theme={null}
{
"event_id": "evt_014",
"event_type": "sla.breached",
"created_at": "2026-03-03T01:00:00Z",
"data": {
"case_token": "cas_xyz789",
"due_date": "2026-03-02T10:00:00Z",
"days_overdue": 1,
"auto_escalated": true,
"escalated_to": { "id": "user_manager01", "name": "Mike Chen" }
}
}
```
## Signature verification
Verify the webhook signature to ensure the payload is authentic:
```python Python theme={null}
import hmac
import hashlib
def verify_signature(payload_bytes, signature, secret):
expected = hmac.new(
secret.encode("utf-8"),
payload_bytes,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
# In your webhook handler:
signature = request.headers.get("X-Zenoo-Signature")
is_valid = verify_signature(request.body, signature, WEBHOOK_SECRET)
```
```javascript Node.js theme={null}
const crypto = require("crypto");
function verifySignature(payload, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(`sha256=${expected}`),
Buffer.from(signature)
);
}
// In your webhook handler:
const signature = req.headers["x-zenoo-signature"];
const isValid = verifySignature(req.rawBody, signature, WEBHOOK_SECRET);
```
## Next steps
* [Audit Trail Guide](/guides/audit-trail). Query historical events.
* [Onboarding Quickstart](/use-cases/onboarding). See webhooks in action during onboarding.
# Authentication
Source: https://docs.zenoo.com/getting-started/authentication
Every API request must include a valid API key in the X-API-KEY header.
# Authentication
Every API request must include a valid API key in the `X-API-KEY` header.
```bash theme={null}
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-d '{ ... }'
```
## API keys
Each key is scoped to a single project. One project, one key.
**Environment separation.** Staging and production use different keys. A staging key does not authenticate against the production URL. Your project hash is the same across environments, but the keys are not interchangeable.
**Storage.** Store keys in environment variables or a secrets manager (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager). Never commit keys to version control, embed them in client-side JavaScript, or log them in application output.
Store API keys in a dedicated secrets manager (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager) for production deployments. Environment variables are the minimum acceptable approach.
```bash Shell theme={null}
# Environment variable
export ZENOO_API_KEY="your-api-key"
# Use in your application
curl -H "X-API-KEY: $ZENOO_API_KEY" ...
```
```javascript Node.js theme={null}
const apiKey = process.env.ZENOO_API_KEY;
```
```python Python theme={null}
import os
api_key = os.environ["ZENOO_API_KEY"]
```
## Key rotation
Contact Zenoo support to rotate your API key. The process:
Contact Zenoo support to request a new API key for your project.
Zenoo generates the new key and revokes the old one immediately.
Deploy the new key to all applications, environment variables, and CI/CD secrets immediately after receiving it.
There is no grace period where both keys are active. Plan your deployment before requesting rotation. Update all configuration, environment variables, and CI/CD secrets, then deploy immediately after receiving the new key.
Rotate your key immediately if you suspect it has been compromised. Rotate proactively on a regular schedule as part of your security practices.
## Error responses
| Status | Error Code | Meaning |
| - | - | - |
| `401` | `UNAUTHORIZED` | The `X-API-KEY` header is missing or the key is invalid |
| `403` | `FORBIDDEN` | The key is valid but does not have access to this project |
### 401 Unauthorized
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Missing or invalid API key",
"request_id": "req-a1b2c3d4"
}
```
Check that the `X-API-KEY` header is present and contains the correct key for your environment. If the key was recently rotated, retrieve the new key from your secrets manager.
Do not retry 401 errors. The same key will fail every time.
### 403 Forbidden
```json theme={null}
{
"error": "FORBIDDEN",
"message": "API key does not have access to this project",
"request_id": "req-e5f6g7h8"
}
```
The key is valid but not authorized for the project hash in the URL. Verify you are using the correct project hash, or contact Zenoo support to check key-to-project bindings.
## Security rules
* Never expose API keys in client-side code (browser JavaScript, mobile apps).
* Never commit keys to Git repositories, even private ones.
* Never log API keys in application output or error messages.
* Store keys in environment variables or a dedicated secrets manager.
* Use HTTPS for all API requests. HTTP is not supported.
* Rotate keys immediately if a compromise is suspected.
See also: [Webhook Signatures](/guides/webhooks#signature-verification) and [Security Best Practices](/getting-started/security-best-practices).
# Environments
Source: https://docs.zenoo.com/getting-started/environments
Zenoo provides separate staging and production environments with their own base URLs, API keys, and provider connections.
# Environments
Zenoo provides separate staging and production environments. Each environment has its own base URL, API keys, and provider connections.
## Base URLs
| Environment | Base URL |
| - | - |
| **Production** | `https://instance.prod.onboardapp.io` |
| **Staging** | `https://instance.staging.onboardapp.io` |
## API keys
API keys are environment-specific. A staging key does not work against the production URL, and vice versa. Your project hash is the same across both environments, but the keys are different.
Staging and production API keys are not interchangeable. Using a staging key against the production URL (or vice versa) will return `401 Unauthorized`.
Request keys during onboarding. You will receive one key per environment per project.
## Endpoints
All endpoints follow the gateway pattern:
```
{base_url}/api/gateway/execute/{project_hash}/{action}
```
| Method | Path | Purpose | Mode |
| - | - | - | - |
| `POST` | `/api/gateway/execute/{project_hash}/api` | Initiate verification (sync) | Blocks until complete or timeout |
| `POST` | `/api/gateway/execute/{project_hash}/init` | Initiate verification (async) | Returns tokens immediately |
| `GET` | `/api/gateway/sharable-payload/{pull_token}` | Retrieve async results | Returns results or `204` if pending |
### Sync mode
Add the `X-SYNC-TIMEOUT` header (value in milliseconds) to make the `/api` endpoint block until checks complete:
```bash theme={null}
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{ ... }'
```
If the timeout is reached before all checks finish, you may receive partial results or tokens for polling.
### Async mode
Call the `/init` endpoint without `X-SYNC-TIMEOUT`. The response contains two tokens:
```json theme={null}
{
"tokens": {
"pull": "eyJhbGciOiJIUzI1NiJ9.pull-token...",
"start": "eyJhbGciOiJIUzI1NiJ9.start-token..."
}
}
```
* **`pull`** -- Retrieve results via the `/sharable-payload/{pull_token}` endpoint.
* **`start`** -- Construct a verification URL for user-facing flows: `{base_url}/{project_hash}/?t={start_token}`
### Pulling results
Poll the pull endpoint until you receive a `200` with results:
```bash theme={null}
curl "https://instance.prod.onboardapp.io/api/gateway/sharable-payload/{pull_token}"
```
| Status | Meaning |
| - | - |
| `200` | Results ready. Response body contains full compliance report. |
| `204` | Still processing. Retry after 10-30 seconds. |
| `404` | Invalid or expired token. |
## Environment differences
| Aspect | Staging | Production |
| - | - | - |
| Providers | Sandbox/mock APIs | Live provider APIs |
| Screening | Mock data based on test patterns | Real WorldCheck results |
| Webhooks | Full delivery | Full delivery |
| Rate limits | Lower | Production limits |
| Data retention | May be purged periodically | Standard retention |
Staging is for integration testing only. Do not use staging results for compliance decisions. Staging providers return mock or sandbox data that does not reflect real-world verification outcomes.
# First API Call
Source: https://docs.zenoo.com/getting-started/first-api-call
Get from zero to a working API call in under 2 minutes with your staging API key and project hash.
# First API Call
This page gets you from zero to a working API call in under 2 minutes. You need your staging API key and project hash.
**Prerequisites:** You need a staging API key and a project hash before proceeding. See [Authentication](/getting-started/authentication) for how to obtain these credentials.
AML screening is the simplest call. It takes a name, runs it against PEP/sanctions/adverse media databases, and returns structured match results synchronously.
```bash cURL theme={null}
curl -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-staging-api-key" \
-H "X-SYNC-TIMEOUT: 15000" \
-d '{
"name": "Jane Smith",
"date_of_birth": "1990-06-15",
"country": "GB",
"entity_type": "person",
"categories": ["pep", "sanctions", "adverse_media"]
}'
```
```javascript Node.js theme={null}
const response = await fetch(
`https://instance.staging.onboardapp.io/api/gateway/execute/${projectHash}/api`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": process.env.ZENOO_API_KEY,
"X-SYNC-TIMEOUT": "15000"
},
body: JSON.stringify({
name: "Jane Smith",
date_of_birth: "1990-06-15",
country: "GB",
entity_type: "person",
categories: ["pep", "sanctions", "adverse_media"]
})
}
);
const result = await response.json();
console.log(result);
```
```python Python theme={null}
import os
import requests
response = requests.post(
f"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api",
headers={
"Content-Type": "application/json",
"X-API-KEY": os.environ["ZENOO_API_KEY"],
"X-SYNC-TIMEOUT": "15000",
},
json={
"name": "Jane Smith",
"date_of_birth": "1990-06-15",
"country": "GB",
"entity_type": "person",
"categories": ["pep", "sanctions", "adverse_media"],
},
)
print(response.json())
```
A clean screening result looks like this:
```json {2-4} theme={null}
{
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "No Hit",
"screening_provider": "WorldCheck",
"screening_completed_at": "2026-01-15T14:30:00Z",
"matches": []
}
```
All status fields show `"No Hit"` and the `matches` array is empty. The entity does not appear in any screening database.
If there are matches, each entry in the `matches` array includes a `matchScore` (0-100), `primaryName`, `category`, and provider-specific detail fields.
Submit a company for verification. This runs registry checks and screening in a single call.
```bash cURL theme={null}
curl -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-staging-api-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"external_reference": "test-001"
}'
```
```javascript Node.js theme={null}
const response = await fetch(
`https://instance.staging.onboardapp.io/api/gateway/execute/${projectHash}/api`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": process.env.ZENOO_API_KEY,
"X-SYNC-TIMEOUT": "30000"
},
body: JSON.stringify({
company_name: "Acme Holdings Ltd",
registration_number: "12345678",
country: "GB",
external_reference: "test-001"
})
}
);
const result = await response.json();
console.log(result.overall_verdict); // "Pass", "Refer", or "Fail"
console.log(result.risk_tier); // "Low", "Medium", or "High"
```
```python Python theme={null}
response = requests.post(
f"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api",
headers={
"Content-Type": "application/json",
"X-API-KEY": os.environ["ZENOO_API_KEY"],
"X-SYNC-TIMEOUT": "30000",
},
json={
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"external_reference": "test-001",
},
)
data = response.json()
print(data["overall_verdict"]) # "Pass", "Refer", or "Fail"
print(data["risk_tier"]) # "Low", "Medium", or "High"
```
The response includes company verification data, screening results, risk assessment, and a checks summary. See the [Company Verification Quickstart](/use-cases/kyb-verification) for the full response structure.
## What's next
API key management and webhook signatures
Full company verification walkthrough
Individual identity verification with document capture
Standalone PEP/sanctions screening
# Getting Started
Source: https://docs.zenoo.com/getting-started/overview
Zenoo is a verification orchestration platform that connects your system to identity, screening, and company registry providers through a single API.
Zenoo is a verification orchestration platform. It connects your system to identity, screening, and company registry providers through a single API. You send data, Zenoo runs the checks, and you get structured compliance results back.
**What Zenoo does:**
* **Company Verification** -- Company registry verification, director and UBO screening, corporate structure analysis
* **Person Verification** -- Document verification, biometric liveness, face matching, database checks
* **AML Screening** -- PEP, sanctions, adverse media, and watchlist screening via WorldCheck
You submit entity data over a REST API. Zenoo routes it to the appropriate providers, normalizes the results, scores risk, and returns a compliance report. No direct provider integrations required on your end.
## Architecture
```mermaid theme={null}
flowchart LR
subgraph YS["Your System"]
A1["Submit entity\ndata via API"]
A2["Receive\ncompliance report"]
end
subgraph ZG["Zenoo Gateway"]
G["Gateway"]
G --> WC["WorldCheck Screening\nPEP / Sanctions / Adverse Media"]
G --> CR["Company Registries\nCompany status / Directors / UBOs"]
G --> DV["Document Verification\nOCR / Tamper / Biometric liveness"]
G --> RM["Risk Model Evaluation\n4-dimension scoring"]
G --> AG["Alert Generation\nPEP / Sanctions / Mismatches"]
G --> AI["AI Research (optional)\nCompany/person analysis"]
end
A1 --> G
ZG -->|"Normalized compliance report"| A2
```
## Processing pipeline
When you submit entity data, Zenoo runs through these stages:
Your payload is validated and a case is created. If you included an `external_reference` that already exists, the existing case is returned instead.
The entity (and any associated individuals like directors or UBOs) is screened against WorldCheck databases for PEP status, sanctions list matches, adverse media, and watchlist entries.
For companies, Zenoo queries official company registries to verify legal name, registration number, company status, registered address, directors, and shareholders.
For Person Verification flows that require user interaction, the individual captures identity documents and a selfie. Zenoo runs OCR, tamper detection, and biometric liveness checks.
All check results feed into a risk scoring model that evaluates entity risk, geographic risk, industry risk, and screening risk. The output is a risk tier (Low, Medium, High) and a numeric score.
If screening returns hits, data mismatches are detected, or risk thresholds are exceeded, alerts are created for compliance review.
For company verifications, Zenoo identifies beneficial owners from registry data and screens them individually.
Automated research on the entity using public data sources. Produces a structured summary of the entity's background, industry presence, and risk indicators.
## Processing timeline
| Stage | Typical duration |
| - | - |
| API gateway routing | \< 1 second |
| WorldCheck screening | 3-10 seconds |
| Company registry lookup | 5-15 seconds |
| Document verification | 10-30 seconds (after user submits) |
| Biometric liveness | 5-15 seconds (after user submits) |
| Risk model evaluation | 1-5 seconds |
| Case creation and alert generation | 5-15 seconds |
| Auto-execute queued checks | 10-30 seconds |
| AI research | 30-120 seconds |
For fully automated flows (Company Verification, screening), expect **2-5 minutes** end to end. Flows that require user interaction (Person Verification document capture) add human time on top. Once the user submits, server-side processing completes in 30-60 seconds.
## Two execution models
Zenoo supports synchronous and asynchronous execution.
**Endpoint:** `POST /api` with `X-SYNC-TIMEOUT`
Your request blocks until checks complete or the timeout is reached. Results are returned directly in the HTTP response. Best for server-to-server flows like screening and Company Verification.
```
POST /api/gateway/execute/{project_hash}/api
Header: X-SYNC-TIMEOUT: 30000
```
**Endpoint:** `POST /init`
The API returns immediately with tokens. You retrieve results later by polling or via webhook. Required for flows where a user must interact with verification UI (document capture, biometric liveness).
```
POST /api/gateway/execute/{project_hash}/init
```
Most integrations use both: sync for automated Company Verification and screening, async for interactive Person Verification flows.
## Quick links
* **Base URL (production):** `https://instance.prod.onboardapp.io`
* **Base URL (staging):** `https://instance.staging.onboardapp.io`
* **Auth header:** `X-API-KEY: your-api-key`
* **Gateway pattern:** `POST /api/gateway/execute/{project_hash}/{action}`
## What's next
API keys, webhook signatures, security practices
Base URLs, endpoint reference, environment separation
Working curl/Node.js/Python examples you can run in 2 minutes
Secure your integration against common vulnerabilities
# Security Best Practices
Source: https://docs.zenoo.com/getting-started/security-best-practices
Best practices for API key storage, key rotation, transport security, and webhook endpoint protection.
# Security Best Practices
## API key storage
Store API keys in a secrets manager or environment variables. Never hardcode them in source code.
| Approach | Recommended |
| - | - |
| Environment variables | Yes. Minimum acceptable approach. |
| Secrets manager (AWS Secrets Manager, Vault, GCP Secret Manager) | Yes. Preferred for production. |
| Configuration file (`.env`, `config.yaml`) | Only if gitignored and encrypted at rest. |
| Hardcoded in source code | No. Never. |
| Client-side JavaScript | No. Keys are visible to anyone with browser dev tools. |
| Mobile app bundles | No. APKs and IPAs can be decompiled. |
Never hardcode API keys in source code, client-side JavaScript, or mobile app bundles. Keys in these locations are trivially extractable and cannot be considered secret.
```bash Shell theme={null}
# Set via environment variable
export ZENOO_API_KEY="your-api-key"
export ZENOO_WEBHOOK_SECRET="your-webhook-secret"
```
```javascript Node.js theme={null}
// Read from environment
const apiKey = process.env.ZENOO_API_KEY;
const webhookSecret = process.env.ZENOO_WEBHOOK_SECRET;
```
```python Python theme={null}
import os
api_key = os.environ["ZENOO_API_KEY"]
webhook_secret = os.environ["ZENOO_WEBHOOK_SECRET"]
```
## Key rotation
Rotate API keys on a regular schedule and immediately after any suspected compromise.
**Planned rotation.** Prepare your deployment pipeline before requesting a new key. The old key is revoked the moment the new one is issued. There is no overlap period.
Update all application configurations and CI/CD secrets to accept the new key.
Request rotation from Zenoo support.
Receive the new key from Zenoo support.
Deploy the new key to all environments immediately after receiving it.
**Emergency rotation.** If a key is exposed in a public repository, log file, or client-side code, contact Zenoo support immediately. The compromised key will be revoked and a new one issued.
## Transport security
All API communication must use HTTPS. Zenoo does not accept HTTP requests. Use TLS 1.2 or higher.
* Verify server certificates. Do not disable certificate validation in production.
* Pin certificates if your security policy requires it.
## IP allowlisting
Contact Zenoo support to configure IP allowlisting for your API keys. When enabled, requests from IPs outside the allowlist are rejected with `403 Forbidden`.
Considerations:
* Provide the public IP addresses or CIDR ranges of your application servers.
* Update the allowlist before deploying to new infrastructure.
* Cloud-hosted applications with dynamic IPs may need a NAT gateway or static IP assignment.
## Webhook endpoint security
Your webhook endpoint receives verification results that may contain personally identifiable information. Protect it accordingly.
**Verify signatures.** Always validate the `X-Zenoo-Signature` header before processing any webhook payload. See [Webhook Signatures](/guides/webhooks#signature-verification) for implementation details.
**Use HTTPS.** Your webhook endpoint must be accessible over HTTPS. Zenoo will not deliver webhooks to HTTP URLs.
**Respond quickly.** Return `200 OK` within 30 seconds. Queue the payload for async processing rather than performing business logic in the request handler.
**Log failures.** Record every failed signature verification with the timestamp, source IP, and request headers. A pattern of failures may indicate someone is probing or spoofing your endpoint.
**Restrict access.** If possible, configure your firewall or load balancer to only accept webhook requests from Zenoo's IP ranges. Contact Zenoo support for the current list.
## Monitoring
Track these metrics to detect issues early. Set up automated alerts for any anomalies -- early detection is the best defense against key compromise and integration failures.
| Metric | What to watch |
| - | - |
| API error rate by status code | Spike in `401`/`403` may indicate key compromise or misconfiguration |
| `5xx` error rate | Sustained increase suggests provider or infrastructure issues |
| Webhook delivery success rate | Failures may indicate endpoint issues or network problems |
| Webhook signature verification failures | Repeated failures suggest a misconfigured secret or attack |
| Response latency (p50, p95, p99) | Increasing latency may indicate provider degradation |
**Set up alerts for:**
* `5xx` error rate exceeding 5% over a 10-minute window.
* Any `401` errors (may indicate a rotated or leaked key).
* Webhook signature verification failure rate above 0%.
* Average response time exceeding 2x your baseline.
**Log the `request_id`** from every API response. When contacting Zenoo support, include this ID to enable fast diagnosis.
```javascript Node.js theme={null}
const response = await fetch(url, options);
const data = await response.json();
if (!response.ok) {
console.error(`Zenoo API error: ${data.error}`, {
requestId: data.request_id,
status: response.status,
message: data.message
});
}
```
```python Python theme={null}
response = requests.post(url, headers=headers, json=payload)
if not response.ok:
data = response.json()
logger.error(
"Zenoo API error: %s (request_id=%s, status=%d)",
data.get("error"),
data.get("request_id"),
response.status_code,
)
```
# AI Research
Source: https://docs.zenoo.com/guides/ai-research
Trigger AI-powered research on alerts, understand confidence scores, and configure the auto-disposition pipeline.
# AI Research
Zenoo integrates AI-powered research to help compliance analysts evaluate screening matches faster and with greater accuracy. The AI analyzes alert data against public sources, produces structured assessments, and can automatically resolve high-confidence false positives.
## How it works
When AI research is triggered on an alert, the system:
1. **Builds a research prompt** with the alert details, entity context, and screening match data
2. **Submits to Perplexity AI** (sonar-pro model) for live web research
3. **Parses the structured response** into assessment fields
4. **Updates the alert** with the AI analysis results
5. **Optionally chains to auto-disposition** if configured for the alert category
```mermaid theme={null}
sequenceDiagram
participant A as Analyst / System
participant Z as Zenoo
participant AI as Perplexity AI
A->>Z: Trigger AI research
Z->>AI: Research prompt + alert context
AI-->>Z: Structured assessment
Z->>Z: Update alert fields
Z->>Z: Evaluate auto-disposition
Z-->>A: Notification (complete)
```
## Triggering AI research
### Manual trigger (API)
```bash theme={null}
curl -X POST "https://api.zenoo.com/v1/alerts/alt_001/ai-research" \
-H "Authorization: Bearer your-api-key"
```
**Response:**
```json theme={null}
{
"status": "processing",
"message": "AI research initiated. Results will be available shortly.",
"estimated_seconds": 15
}
```
### Automatic trigger (auto-triage)
When auto-triage is enabled, new alerts are automatically sent for AI research based on configuration rules:
| Configuration | Description |
| - | - |
| Allowed alert types | Which types trigger auto-triage (e.g., Screening) |
| Excluded categories | Categories to skip (e.g., Sanctions Hit) |
| Batch size | Maximum alerts processed per trigger |
Auto-triage runs asynchronously. For large batches, the system uses Platform Events to distribute the workload across multiple async processes.
## AI assessment structure
After research completes, the alert is updated with the following fields:
| Field | Type | Description |
| - | - | - |
| `ai_assessment` | string | Detailed narrative analysis of the screening match |
| `ai_confidence` | number | AI confidence in the assessment (0-100%) |
| `false_positive_probability` | number | Estimated probability the match is a false positive (0-100%) |
| `ai_recommended_action` | string | Suggested resolution: `Approve`, `Decline`, `Escalate` |
| `ai_sources` | array | URLs and titles of sources consulted |
| `ai_researched_at` | string | ISO 8601 timestamp of research completion |
### Retrieving AI results
```bash theme={null}
curl -X GET "https://api.zenoo.com/v1/alerts/alt_001/ai-research" \
-H "Authorization: Bearer your-api-key"
```
**Response:**
```json theme={null}
{
"status": "completed",
"assessment": "The matched entity 'J. Smith' on the OFAC SDN list was designated in 2019 for activities related to Iranian oil exports. The subject 'John Smith' is a UK national with no connection to the sanctioned individual. Different date of birth (1975 vs 1960) and different country of residence.",
"confidence": 92,
"false_positive_probability": 95,
"recommended_action": "Approve",
"sources": [
{
"title": "OFAC SDN List",
"url": "https://sanctionssearch.ofac.treas.gov/"
},
{
"title": "Companies House",
"url": "https://find-and-update.company-information.service.gov.uk/"
}
],
"researched_at": "2026-02-16T10:05:30Z"
}
```
## Confidence scores
The AI produces two related but distinct scores:
| Score | What it measures |
| - | - |
| **AI Confidence** | How certain the AI is about its own assessment |
| **False Positive Probability** | How likely the match is a false positive |
A high confidence score with a high false positive probability means the AI is very sure this is not a true match. A high confidence score with a low false positive probability means the AI is very sure this is a genuine match that requires action.
| Confidence | FP Probability | Interpretation |
| - | - | - |
| High (above 80) | High (above 80) | Strong evidence of false positive |
| High (above 80) | Low (below 30) | Strong evidence of true positive |
| Low (below 50) | Any | Insufficient data -- needs human review |
## Auto-disposition pipeline
Alerts that meet category-specific thresholds can be automatically resolved as false positives:
```mermaid theme={null}
flowchart TD
A[AI Research Complete] --> B{Config enabled?}
B -->|No| C[Leave for human review]
B -->|Yes| D{FP probability >= threshold?}
D -->|No| C
D -->|Yes| E[Auto-resolve as False Positive]
E --> F{QA sampling enabled?}
F -->|Yes| G[Flag for QA review]
F -->|No| H[Done]
G --> H
```
### Configuration per category
| Category | Auto-Disposition | FP Threshold | QA Sample Rate |
| - | - | - | - |
| `PEP Match` | Enabled | 90% | 10% |
| `Adverse Media` | Enabled | 90% | 15% |
| `Sanctions Hit` | Disabled | N/A | N/A |
| `Name Mismatch` | Enabled | 85% | 5% |
| `DOB Mismatch` | Enabled | 85% | 5% |
Sanctions hits are never auto-disposed by default. Automated resolution of sanctions matches may violate regulatory requirements. Always configure Sanctions Hit with auto-disposition disabled.
### QA sampling
A configurable percentage of auto-disposed alerts are flagged for quality assurance review. QA-flagged alerts are resolved but marked with `qa_sample: true`, allowing compliance officers to periodically review the AI's accuracy.
## Company research
AI research can also be used for KYB (Know Your Business) company research:
```bash theme={null}
curl -X POST "https://api.zenoo.com/v1/entities/ent_abc123/ai-research" \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"research_type": "company",
"include": ["registration", "directors", "financials", "media"]
}'
```
This returns a structured company profile with registry data, director information, financial overview, and recent media mentions.
## Next steps
* [Alert Management](/guides/alert-management). Full alert lifecycle and resolution options.
* [Alert Review Quickstart](/use-cases/alert-review). Hands-on alert triage walkthrough.
* [Alerts Data Model](/data-model/alerts). Full field reference including AI fields.
# Alert Management
Source: https://docs.zenoo.com/guides/alert-management
Alert lifecycle, auto-triage, AI research, priority scoring, SLA monitoring, and bulk operations in the Zenoo CLM platform.
# Alert Management
Alerts are the primary work items for compliance analysts. They represent compliance issues -- PEP matches, sanctions hits, adverse media, identity discrepancies -- that require human review and resolution.
## Alert lifecycle
```mermaid theme={null}
stateDiagram-v2
[*] --> Open : Alert created
Open --> Acknowledged : Analyst claims
Open --> Resolved : Direct resolution
Open --> False_Positive : Confirmed FP
Open --> Escalated : Escalate to manager
Acknowledged --> Resolved : After review
Acknowledged --> False_Positive : Confirmed FP
Acknowledged --> Escalated : Escalate
Resolved --> [*]
False_Positive --> [*]
Escalated --> [*]
```
| Status | Description |
| - | - |
| `Open` | Alert created, awaiting analyst review |
| `Acknowledged` | Analyst has claimed and is reviewing the alert |
| `Resolved` | Alert reviewed and resolved with an action |
| `False Positive` | Confirmed as a false positive match |
| `Escalated` | Escalated to a senior reviewer or manager |
## Auto-triage
New alerts can be automatically triaged using AI research. The auto-triage system is configured via custom metadata:
1. **Alert insertion** triggers the auto-triage handler
2. The handler filters alerts by type and category against the triage configuration
3. Eligible alerts are dispatched to an async AI research queue
4. AI research analyzes the screening match against public sources
5. Results are written back to the alert (assessment, confidence, false positive probability)
6. If configured, the auto-disposition pipeline evaluates the results
Auto-triage is configurable per alert type and category. You can exclude specific categories (e.g., Sanctions) from auto-triage to ensure they always receive human review.
## AI research
AI research provides automated analysis of screening matches. When triggered (manually or via auto-triage), the system:
1. Builds a research prompt with alert details, entity context, and screening data
2. Submits to a web research AI (Perplexity) for validation
3. Returns a structured assessment with:
* **Assessment narrative** -- detailed analysis of the match
* **Confidence score** -- AI confidence in its assessment (0-100%)
* **False positive probability** -- likelihood the match is a false positive (0-100%)
* **Recommended action** -- suggested resolution (Approve, Decline, Escalate)
* **Sources** -- URLs of sources consulted
### Auto-disposition
Alerts with high-confidence false positive assessments can be automatically resolved:
| Condition | Action |
| - | - |
| FP probability >= category threshold | Auto-resolve as False Positive |
| FP probability \< threshold | Leave for human review |
| QA sampling enabled | Flag a percentage of auto-dispositions for QA |
Auto-disposition thresholds are configured per alert category. For example, `Adverse Media` might auto-dispose at 90% FP probability while `Sanctions Hit` is never auto-disposed.
## Priority scoring
Every alert receives a composite priority score (0-290 points) calculated from five components:
| Component | Max Points | Calculation |
| - | - | - |
| SLA urgency | 100 | Breached = 100, linear decay from due date |
| Risk tier | 100 | Critical = 100, High = 75, Medium = 50, Low = 25 |
| Category weight | 60 | Sanctions = 60, PEP = 45, Adverse Media = 30 |
| Match score | 10 | Screening confidence / 10 |
| Case risk | 20 | Parent case risk score / 5 |
The priority score drives the default sort order in the analyst inbox: highest-score alerts appear first.
### Priority labels
| Priority | Score Range |
| - | - |
| `Critical` | 200+ |
| `High` | 120-199 |
| `Medium` | 60-119 |
| `Low` | 0-59 |
## SLA monitoring
Each alert inherits an SLA due date based on its category and priority. A nightly batch job monitors SLA compliance:
| SLA Status | Condition | Action |
| - | - | - |
| `On Track` | Due date > warning threshold | No action |
| `Warning` | Within warning window | Email notification to analyst |
| `Critical` | Due date is today | Urgent notification |
| `Breached` | Past due date | Auto-escalate (if configured) |
## Resolution actions
When resolving an alert, specify one of the following actions:
| Action | Description | Use case |
| - | - | - |
| `Approve` | Accept the entity despite the alert | False positive or acceptable risk |
| `Decline` | Reject the entity | Confirmed compliance issue |
| `Escalate` | Route to senior reviewer | Complex or uncertain case |
| `Request Document` | Request additional evidence from client | Insufficient documentation |
| `Approve with Conditions` | Accept with monitoring or restrictions | Marginal risk, mitigated by conditions |
## Bulk operations
For high-volume processing, use the bulk endpoints:
```bash theme={null}
# Bulk acknowledge
curl -X POST https://api.zenoo.com/v1/alerts/bulk/acknowledge \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{ "alert_tokens": ["alt_001", "alt_002", "alt_003"] }'
# Bulk resolve
curl -X POST https://api.zenoo.com/v1/alerts/bulk/resolve \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"alert_tokens": ["alt_004", "alt_005"],
"action": "Approve",
"notes": "Batch: confirmed formatting differences."
}'
# Bulk assign
curl -X POST https://api.zenoo.com/v1/alerts/bulk/assign \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"alert_tokens": ["alt_006", "alt_007"],
"assignee": "user_analyst01"
}'
```
See [Bulk Operations Guide](/guides/bulk-operations) for details on partial success handling and error responses.
## Next steps
* [AI Research Guide](/guides/ai-research). Deep dive into AI-powered alert analysis.
* [Risk Scoring Guide](/guides/risk-scoring). How alerts contribute to risk assessments.
* [Alerts Data Model](/data-model/alerts). Full field reference.
* [Bulk Operations Guide](/guides/bulk-operations). Processing alerts at scale.
# Architecture & Data Flow
Source: https://docs.zenoo.com/guides/architecture
Understand how Zenoo orchestrates verification checks across providers and delivers unified compliance reports.
# Architecture & Data Flow
## Overview
Zenoo is a verification orchestration hub. You submit entity data to a single API endpoint. Zenoo routes checks to the appropriate providers (screening databases, company registries, document verification, and others), normalizes the results into a unified format, evaluates risk, and returns a structured compliance report.
One integration point. Multiple providers. Consistent results.
Your system communicates exclusively with Zenoo. Zenoo handles all provider and Salesforce interactions. You never need to integrate directly with screening databases, company registries, or document verification providers.
## System Architecture
```mermaid theme={null}
flowchart LR
A[Your System] -->|REST API| B[Zenoo Gateway]
B -->|Checks| C[Providers]
C -->|Results| B
B -->|Enrichment| D[Salesforce Case Management]
D -->|Enhanced Data| B
B -->|Results| A
```
| Component | Role |
| - | - |
| **Your System** | Submits entity data, receives compliance results |
| **Zenoo Gateway** | Orchestration hub -- routes checks, normalizes results, serves API |
| **Providers** | Screening databases, company registries, document verification, biometric liveness, phone verification |
| **Salesforce** | Back-end enrichment -- case management, risk scoring, AI research, UBO discovery, alert triage, audit trail |
## Processing Pipeline
When you submit entity data, Zenoo executes these stages:
Your payload is validated and a case is created. If the `external_reference` already exists, the existing case is returned instead of creating a duplicate.
The entity and any associated individuals (directors, UBOs) are screened against WorldCheck databases for PEP status, sanctions matches, adverse media, and watchlist entries.
For companies, Zenoo queries official company registries to verify legal name, registration number, company status, registered address, directors, and shareholders.
For Person Verification flows requiring user interaction, the individual captures identity documents and a selfie. Zenoo runs OCR, tamper detection, and biometric liveness checks.
All check results feed into a risk scoring model that evaluates entity risk, geographic risk, industry risk, and screening risk. The output is a risk tier (Low, Medium, High) and a numeric score.
If screening returns hits, data mismatches are detected, or risk thresholds are exceeded, alerts are created for compliance review.
For company verifications, beneficial owners are identified from registry data and screened individually.
Automated research on the entity using public data sources, producing a structured summary of background, industry presence, and risk indicators.
### Processing Timeline
| Stage | Typical Duration |
| - | - |
| API gateway routing | \< 1 second |
| WorldCheck screening | 3-10 seconds |
| Company registry lookup | 5-15 seconds |
| Document verification (after user submits) | 10-30 seconds |
| Risk model evaluation | 1-5 seconds |
| Alert generation | \< 1 second |
| UBO discovery | Part of registry lookup |
| AI research | 30-120 seconds |
For fully automated flows (Company Verification, screening), expect **2-5 minutes** end to end. Flows that require user interaction (Person Verification document capture) add human time on top. Once the user submits, server-side processing completes in 30-60 seconds.
## Two Execution Models
Zenoo supports synchronous and asynchronous execution.
| Aspect | Sync (Model 1) | Async (Model 2) |
| - | - | - |
| **Endpoint** | `POST /api/gateway/execute/{project_hash}/api` | `POST /api/gateway/execute/{project_hash}/init` |
| **Header** | `X-SYNC-TIMEOUT: 30000` (ms) | None required |
| **Response** | Full results (or partial + tokens on timeout) | Tokens only (`pull` + `start`) |
| **Blocking** | Yes, up to timeout | No |
| **User interaction** | Not supported | Supported via `start` token |
| **Best for** | Screening, Company, server-to-server | Person with document capture, biometrics |
| **Typical latency** | 10-60 seconds | Instant (tokens), results in 30s-5min |
**Quick rule:** server-to-server checks use sync. User-facing journeys use async.
See [Sync vs Async](/guides/sync-vs-async) for implementation details, code examples, and timeout recommendations.
## Data Flow
The end-to-end flow from submission to results follows six steps:
1. **Submit entity data** to the Zenoo Gateway via the API (`/api` for sync, `/init` for async).
2. **Zenoo initiates configured checks** across providers -- screening, registry, document verification -- based on the flow configuration.
3. **Provider results stream back** to Zenoo as each check completes.
4. **Zenoo normalizes results**, applies the risk model, generates alerts for any hits or mismatches, and assembles the compliance report.
5. **(Optional) Salesforce case management** enriches results with AI research, UBO discovery, risk assessment, and compliance review.
6. **Retrieve aggregated results** via sync response body, pull token polling, or webhook delivery.
## Result Delivery
Zenoo supports three methods for delivering verification results to your system.
### Pull via Token (simplest)
Poll `GET /sharable-payload/{pull_token}` at intervals. Returns `204` while processing, `200` with full results when complete. Pull tokens remain valid for 30 days after results are available.
### Webhook Push (real-time)
Zenoo POSTs the full compliance report to your webhook endpoint when processing finishes. Payloads are signed with HMAC-SHA256 for verification. Fastest delivery, but requires you to expose and maintain a webhook endpoint.
### Ping + Pull (recommended for production)
Use Ping + Pull for production deployments. Zenoo sends a lightweight notification to your webhook, then you fetch the full payload from the pull endpoint at your convenience. This combines real-time notification with small webhook payloads and on-demand data retrieval.
Zenoo sends a lightweight notification (status + pull token, no result data) to your webhook. You then fetch the full payload from the pull endpoint at your convenience. Combines real-time notification with small webhook payloads and on-demand data retrieval.
| Method | Complexity | Latency | Best For |
| - | - | - | - |
| Pull via Token | Lowest | 10-30s (poll interval) | POC, development, simple integrations |
| Webhook Push | Medium | Near-instant | Event-driven architectures |
| Ping + Pull | Medium | Near-instant notification | Production systems, high reliability |
See [Result Delivery](/guides/result-delivery) for implementation details, polling strategies, and webhook signature verification.
## Salesforce Enrichment Pipeline
When Salesforce case management is enabled, Zenoo automatically webhooks check results to Salesforce. The enrichment pipeline performs:
1. **Case creation** -- Creates an AML case (Onboarding, Review, or Perpetual) with the primary entity.
2. **Entity extraction** -- Creates case entities for each director, UBO, and shareholder from the submitted data and registry results.
3. **Risk model evaluation** -- Applies the configured risk model to determine risk tier and required verification checks.
4. **Auto-execute checks** -- Queues and executes API-based verification checks based on risk model requirements.
5. **AI research** -- Runs automated company and person intelligence gathering using public data sources.
6. **UBO discovery** -- Discovers beneficial ownership structure from registry data, supplements any UBOs provided in the original submission.
7. **Alert generation** -- Creates alerts for PEP hits, sanctions hits, adverse media, name mismatches, DOB mismatches, and other anomalies.
8. **Risk assessment** -- Evaluates multi-dimensional risk (entity-attribute, country, industry, screening) and assigns an overall risk tier.
9. **Audit trail** -- Records all compliance-relevant events with timestamps for regulatory reporting.
Enriched data flows back to Zenoo and is included in the compliance report served via the pull token or webhook.
## What's Next
Create your first verification in 5 minutes
Run company verification checks with screening and registry lookup
Run individual identity verification with document capture
Screen entities against PEP, sanctions, and adverse media databases
Choose between synchronous and asynchronous execution models
Configure how you receive verification results
Set up webhook endpoints for real-time event delivery
# Audit Trail
Source: https://docs.zenoo.com/guides/audit-trail
Immutable audit logging, event types, filtering, and compliance export for regulatory requirements.
# Audit Trail
Every decision, status change, override, escalation, and automated action in the Zenoo platform is recorded in an immutable audit trail. The audit trail is designed for regulatory compliance with FinCEN (5-year retention), OFAC (10-year), FCA, FATF, and EU AMLD5 requirements.
## Immutability
Audit log entries cannot be edited or deleted after creation. This is enforced at the platform level via a validation rule that rejects all update operations on existing records. Only inserts are permitted.
Audit log records are immutable by design. The API does not support updating or deleting audit entries. Attempting to do so returns a `403 Forbidden` error.
## Event types
The audit trail captures 32 event types across seven categories:
### Case events
| Event Type | Severity | Description |
| - | - | - |
| `CASE_CREATED` | INFO | New case created |
| `CASE_STATUS_CHANGE` | INFO | Case status transition |
| `CASE_ASSIGNMENT` | INFO | Case assigned to a user |
| `CASE_ESCALATION` | WARNING | Case escalated to a manager |
| `CASE_CLOSURE` | INFO | Case closed with resolution notes |
### Alert events
| Event Type | Severity | Description |
| - | - | - |
| `ALERT_CREATED` | INFO | New alert generated |
| `ALERT_ACKNOWLEDGED` | INFO | Alert claimed by analyst |
| `ALERT_RESOLVED` | INFO | Alert resolved with action and notes |
| `ALERT_FALSE_POSITIVE` | INFO | Alert marked as false positive |
| `ALERT_ESCALATED` | WARNING | Alert escalated to manager |
| `ALERT_ASSIGNMENT` | INFO | Alert assigned to a user |
| `ALERT_AUTO_DISPOSITION` | INFO | Alert auto-disposed by AI pipeline |
### Risk events
| Event Type | Severity | Description |
| - | - | - |
| `RISK_ASSESSMENT_CREATED` | INFO | New risk assessment initiated |
| `RISK_SCORE_CALCULATED` | INFO | Risk dimensions scored |
| `RISK_TIER_OVERRIDE` | WARNING | Analyst overrode calculated risk tier |
| `RISK_ASSESSMENT_APPROVED` | INFO | Risk assessment approved by reviewer |
### Check events
| Event Type | Severity | Description |
| - | - | - |
| `CHECK_STATUS_CHANGE` | INFO | Check status transition |
| `CHECK_WAIVED` | WARNING | Check waived by analyst with reason |
| `CHECK_REJECTED` | WARNING | Check result rejected |
| `CHECK_COMPLETED` | INFO | Check execution completed |
### Document events
| Event Type | Severity | Description |
| - | - | - |
| `DOCUMENT_UPLOADED` | INFO | Document uploaded by client or analyst |
| `DOCUMENT_REVIEWED` | INFO | Document reviewed (accepted or declined) |
### Reviewer events
| Event Type | Severity | Description |
| - | - | - |
| `REVIEWER_ADDED` | INFO | Reviewer assigned to alert or case |
| `REVIEWER_REMOVED` | INFO | Reviewer removed from alert or case |
| `REVIEWER_RESPONSE` | INFO | Reviewer approved or declined |
### Other events
| Event Type | Severity | Description |
| - | - | - |
| `ENTITY_ADDED` | INFO | Entity added to a case |
| `ENTITY_REMOVED` | INFO | Entity removed from a case |
| `COMMENT_ADDED` | INFO | Comment added to alert or case |
| `AUTO_ESCALATION` | WARNING | System auto-escalated due to SLA breach |
| `SLA_BREACH` | ERROR | SLA deadline passed without resolution |
| `BULK_OPERATION` | INFO | Bulk action performed on multiple records |
| `AI_ANALYSIS` | INFO | AI research or analysis completed |
## Querying the audit trail
### Get audit logs for a case
```bash theme={null}
curl -X GET "https://api.zenoo.com/v1/cases/cas_xyz789/audit-logs?limit=50" \
-H "Authorization: Bearer your-api-key"
```
### Filter by event type
```bash theme={null}
curl -X GET "https://api.zenoo.com/v1/audit-logs?event_type=CASE_ESCALATION&limit=25" \
-H "Authorization: Bearer your-api-key"
```
### Filter by date range
```bash theme={null}
curl -X GET "https://api.zenoo.com/v1/audit-logs?from=2026-01-01T00:00:00Z&to=2026-02-01T00:00:00Z" \
-H "Authorization: Bearer your-api-key"
```
### Response format
```json theme={null}
{
"data": [
{
"id": "log_001",
"event_type": "ALERT_RESOLVED",
"severity": "INFO",
"description": "Alert resolved: Approve",
"old_value": "Open",
"new_value": "Resolved",
"reason": "False positive confirmed by AI research.",
"case_token": "cas_xyz789",
"alert_token": "alt_001",
"actor": {
"id": "user_analyst01",
"name": "Sarah Johnson",
"email": "sarah@example.com"
},
"created_at": "2026-02-16T11:30:00Z"
}
],
"meta": {
"total": 18,
"page": { "cursor": "eyJ...", "has_more": true }
}
}
```
## Compliance export
Export the full audit trail for a case as CSV for regulatory filing:
```bash theme={null}
curl -X GET "https://api.zenoo.com/v1/cases/cas_xyz789/audit-logs/export?format=csv" \
-H "Authorization: Bearer your-api-key" \
-o "audit-trail-cas_xyz789.csv"
```
The CSV includes all fields: timestamp, event type, severity, description, old/new values, actor name, and actor email. Actor names are denormalized at write time so they survive user deactivation.
## Design principles
| Principle | Implementation |
| - | - |
| **Immutability** | Validation rule rejects all edits on existing records |
| **Completeness** | Every service method logs to the audit trail |
| **Non-blocking** | Audit failures never break parent transactions |
| **Denormalization** | Actor names stored at write time for historical accuracy |
| **Retention** | Configurable retention period (default 10 years) |
| **Four-eyes** | Approval actions record both maker and checker |
## Next steps
* [Audit Logs Data Model](/data-model/audit-logs). Full field reference.
* [Case Lifecycle](/guides/case-lifecycle). Events generated during case transitions.
* [Alert Management](/guides/alert-management). Events generated during alert resolution.
* [Webhook Events](/data-model/webhook-events). Real-time event delivery.
# Bulk Operations
Source: https://docs.zenoo.com/guides/bulk-operations
Process multiple alerts in a single request with partial success handling and detailed error reporting.
# Bulk Operations
Bulk endpoints allow you to perform actions on multiple records in a single API call. This is useful for high-volume alert processing, batch assignment, and workflow automation.
## Available bulk operations
| Endpoint | Method | Description |
| - | - | - |
| `/v1/alerts/bulk/resolve` | POST | Resolve multiple alerts |
| `/v1/alerts/bulk/acknowledge` | POST | Acknowledge multiple alerts |
| `/v1/alerts/bulk/assign` | POST | Assign multiple alerts to a user |
| `/v1/alerts/bulk/escalate` | POST | Escalate multiple alerts |
## Bulk resolve
Resolve multiple alerts with the same action and notes:
```bash curl theme={null}
curl -X POST "https://api.zenoo.com/v1/alerts/bulk/resolve" \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"alert_tokens": ["alt_001", "alt_002", "alt_003"],
"action": "Approve",
"notes": "Batch: confirmed formatting differences in name fields."
}'
```
```python Python theme={null}
import requests
response = requests.post(
"https://api.zenoo.com/v1/alerts/bulk/resolve",
headers={
"Authorization": "Bearer your-api-key",
"Content-Type": "application/json",
},
json={
"alert_tokens": ["alt_001", "alt_002", "alt_003"],
"action": "Approve",
"notes": "Batch: confirmed formatting differences in name fields.",
},
)
result = response.json()
print(f"Succeeded: {result['summary']['succeeded']}")
print(f"Failed: {result['summary']['failed']}")
```
```javascript Node.js theme={null}
const response = await fetch("https://api.zenoo.com/v1/alerts/bulk/resolve", {
method: "POST",
headers: {
Authorization: "Bearer your-api-key",
"Content-Type": "application/json"
},
body: JSON.stringify({
alert_tokens: ["alt_001", "alt_002", "alt_003"],
action: "Approve",
notes: "Batch: confirmed formatting differences in name fields."
})
});
const result = await response.json();
console.log(`Succeeded: ${result.summary.succeeded}`);
console.log(`Failed: ${result.summary.failed}`);
```
## Bulk acknowledge
Claim multiple alerts for review:
```bash theme={null}
curl -X POST "https://api.zenoo.com/v1/alerts/bulk/acknowledge" \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"alert_tokens": ["alt_004", "alt_005", "alt_006"]
}'
```
## Bulk assign
Assign multiple alerts to a specific analyst:
```bash theme={null}
curl -X POST "https://api.zenoo.com/v1/alerts/bulk/assign" \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"alert_tokens": ["alt_007", "alt_008", "alt_009"],
"assignee": "user_analyst01"
}'
```
## Bulk escalate
Escalate multiple alerts to a manager:
```bash theme={null}
curl -X POST "https://api.zenoo.com/v1/alerts/bulk/escalate" \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"alert_tokens": ["alt_010", "alt_011"],
"escalate_to": "user_manager01",
"reason": "Complex corporate structure requires senior review."
}'
```
## Partial success handling
Bulk operations use **partial success** semantics. The API processes each record independently and returns per-record results. The HTTP status code indicates the overall outcome:
| HTTP Status | Meaning |
| - | - |
| `200` | All records processed successfully |
| `207` | Partial success -- some records succeeded, some failed |
| `400` | Request validation failed (e.g., empty array) |
| `422` | All records failed |
### Response format
```json theme={null}
{
"summary": {
"total": 3,
"succeeded": 2,
"failed": 1
},
"results": [
{
"token": "alt_001",
"status": "success",
"new_status": "Resolved"
},
{
"token": "alt_002",
"status": "success",
"new_status": "Resolved"
},
{
"token": "alt_003",
"status": "error",
"error": {
"code": "invalid_transition",
"message": "Alert is already resolved."
}
}
]
}
```
### Error codes
| Error Code | Description |
| - | - |
| `not_found` | Alert token does not exist |
| `invalid_transition` | Alert is in a state that does not allow the action |
| `permission_denied` | User lacks permission for the action |
| `already_resolved` | Alert is already resolved |
## Request limits
| Constraint | Limit |
| - | - |
| Max alerts per request | 100 |
| Max concurrent bulk requests | 5 |
| Rate limit | 60 requests per minute |
Exceeding the per-request limit returns a `400 Bad Request` with the message: "Maximum 100 alert tokens per bulk request." Split larger batches into multiple requests.
## Best practices
**Idempotency**: Bulk resolve and acknowledge are idempotent for already-processed records. Re-submitting a token that was already resolved returns a `success` result (not an error). This makes retries safe.
1. **Keep batches under 100 records.** Larger batches increase the chance of partial failures and make retries more complex.
2. **Handle partial success.** Always check the `summary.failed` count and inspect individual `results` for errors. Do not assume all records succeeded.
3. **Use consistent actions.** Bulk resolve applies the same action and notes to all alerts. If different alerts need different actions, split them into separate requests.
4. **Retry only failed records.** On partial failure, extract the failed tokens and retry only those:
```python theme={null}
result = response.json()
failed_tokens = [
r["token"] for r in result["results"] if r["status"] == "error"
]
if failed_tokens:
# Retry only the failed records
retry_response = requests.post(
"https://api.zenoo.com/v1/alerts/bulk/resolve",
headers=headers,
json={"alert_tokens": failed_tokens, "action": "Approve", "notes": "Retry batch."},
)
```
## Audit trail
Every record in a bulk operation generates its own audit trail entry. Bulk operations also create a single `BULK_OPERATION` summary event that references the total count and action performed.
## Next steps
* [Alert Review Quickstart](/use-cases/alert-review). Step-by-step alert triage.
* [Pagination](/guides/pagination). Navigate large result sets.
* [Alert Management](/guides/alert-management). Full alert lifecycle reference.
# Case Lifecycle
Source: https://docs.zenoo.com/guides/case-lifecycle
Understand case types, status transitions, SLA calculation, and closure requirements in the Zenoo CLM platform.
# Case Lifecycle
Cases are the primary unit of work in the Zenoo AML platform. Each case represents an investigation into an entity (company or person) and aggregates alerts, risk assessments, checks, and reviewer workflows.
## Case types
| Type | Purpose | Typical SLA |
| - | - | - |
| `Onboarding` | Initial customer due diligence during onboarding | 14 days |
| `Review` | Periodic re-review of existing customers | 30 days |
| `Perpetual` | Ongoing monitoring and continuous screening | Rolling |
Use `Onboarding` for new customers, `Review` for scheduled periodic re-assessments, and `Perpetual` for continuous monitoring triggered by screening alerts.
## Status transitions
Cases follow a defined state machine. Only valid transitions are permitted.
```mermaid theme={null}
stateDiagram-v2
[*] --> New
New --> In_Progress : Analyst begins work
In_Progress --> Client_Input : Awaiting client response
In_Progress --> Internal_Review : Escalated or sent for review
In_Progress --> Closed : All alerts resolved
Client_Input --> In_Progress : Client responds
Client_Input --> Internal_Review : Escalate
Client_Input --> Closed : Close after response
Internal_Review --> In_Progress : Return to analyst
Internal_Review --> Closed : Reviewer approves closure
New --> Escalated : Auto-escalation (SLA breach)
In_Progress --> Escalated : Manual escalation
```
| Status | Description |
| - | - |
| `New` | Case created, not yet assigned or started |
| `In Progress` | Analyst actively working on the case |
| `In Review` | Submitted for peer or senior review |
| `Internal Review` | Under internal compliance review or escalation |
| `Client Input` | Waiting for the client to provide documents or information |
| `Closed` | Investigation complete, all alerts resolved |
| `Escalated` | Escalated to a manager due to complexity or SLA breach |
### Invalid transitions
The API returns a `422 Unprocessable Entity` error for invalid transitions:
```json theme={null}
{
"error": "invalid_transition",
"message": "Cannot transition from 'Closed' to 'In Progress'. Closed is a terminal state.",
"current_status": "Closed",
"requested_status": "In Progress"
}
```
## SLA calculation
SLA due dates are automatically calculated from custom metadata based on case type and priority:
| Type / Priority | Due Days | Warning Days | Auto-Escalate |
| - | - | - | - |
| Onboarding / Critical | 3 | 1 | Yes |
| Onboarding / High | 7 | 2 | Yes |
| Onboarding / Medium | 14 | 3 | No |
| Onboarding / Low | 21 | 5 | No |
| Review / Critical | 5 | 1 | Yes |
| Review / High | 14 | 3 | Yes |
| Review / Medium | 30 | 7 | No |
| Review / Low | 45 | 10 | No |
### SLA status
| Status | Condition |
| - | - |
| `On Track` | More than warning days remaining |
| `Warning` | Within warning threshold but before due date |
| `Critical` | Due date is today |
| `Breached` | Past due date |
### Auto-escalation
A nightly batch job checks for SLA breaches. Cases with auto-escalation enabled are automatically:
1. Set to `Internal Review` status
2. Assigned to the current analyst's manager
3. Flagged with an escalation reason ("SLA breach")
4. Logged in the audit trail
## Closure requirements
A case can only be closed when:
1. **All alerts are resolved** -- open alert count must be zero
2. **Resolution notes provided** -- a summary of the investigation outcome
```bash theme={null}
curl -X POST https://api.zenoo.com/v1/cases/cas_xyz789/close \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"resolution_notes": "All checks passed. PEP match on UBO reviewed and cleared."
}'
```
If open alerts remain, the API returns:
```json theme={null}
{
"error": "closure_blocked",
"message": "Cannot close case with 2 open alerts. Resolve all alerts first.",
"open_alerts_count": 2
}
```
## Escalation
Cases can be escalated manually or automatically.
### Manual escalation
```bash theme={null}
curl -X POST https://api.zenoo.com/v1/cases/cas_xyz789/escalate \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"reason": "Complex corporate structure requires senior review.",
"escalate_to": "user_manager01"
}'
```
### What happens on escalation
1. Case status changes to `Internal Review`
2. Case is assigned to the specified manager
3. Email notification sent to the manager
4. Audit trail entry created (`CASE_ESCALATION`)
5. Escalation date and reason are recorded
## Risk score recalculation
Case risk scores are automatically recalculated when:
* An alert is resolved or created
* A risk assessment is approved
* An analyst applies a risk override
The system uses approved risk assessments as the primary source. If none exist, it falls back to an alert-based scoring algorithm.
## Next steps
* [Entity Lifecycle](/guides/entity-lifecycle). How entities relate to cases.
* [Alert Management](/guides/alert-management). Alert lifecycle within a case.
* [Audit Trail](/guides/audit-trail). Track every case event.
* [Cases Data Model](/data-model/cases). Full field reference.
# How Do I Collaborate With My Team?
Source: https://docs.zenoo.com/guides/case-management/collaborate-with-team
Add reviewers to cases and alerts, leave threaded comments, @mention colleagues, and track approvals.
# How Do I Collaborate With My Team?
Compliance investigations are rarely a solo effort. Zenoo provides built-in collaboration tools so you can involve colleagues, get approvals, and maintain a clear discussion record — all within the case and alert interfaces.
## What you'll learn
* How to add reviewers to cases and alerts
* How to use threaded comments and @mentions
* How emoji reactions work
* How the approval workflow operates
* How real-time notifications keep everyone in sync
## How do I add a reviewer?
Navigate to the case or alert you want to add a reviewer to.
In the reviewers section (visible on the case or alert detail panel), click **Add Reviewer**.
Choose from a list of available team members. The list shows:
* **Name** and **title** — to help you find the right person
* **Current workload** — how many open cases/alerts they have
* Only users who are not already reviewers on this record appear
Choose the reviewer's role:
* **Reviewer** — asked to review and provide feedback
* **Approver** — asked to provide formal approval (required for case closure in some configurations)
Write a brief message explaining what you need from them. This message appears in their notification.
Click **Add**. The reviewer receives a notification and the record appears in the reviewers panel with status "Invited."
Reviewer statuses:
| Status | Meaning |
| - | - |
| **Invited** | Reviewer has been added but has not responded |
| **Reviewing** | Reviewer has acknowledged and is actively reviewing |
| **Approved** | Reviewer has approved (for Approver role) |
| **Rejected** | Reviewer has declined approval with feedback |
| **Removed** | Reviewer has been removed from the record |
## How do I use comments?
The discussion panel appears on every case, alert, and required check. It supports threaded conversations, @mentions, and emoji reactions.
Scroll to the **Discussion** section on any case, alert, or check detail view.
Type your message in the text box. You can write different comment types:
* **Comment** — general discussion
* **Question** — flag something you need clarified
* **Note** — record an observation for the file
* **Decision** — document a formal decision with rationale
Type **@** followed by the person's name. An autocomplete dropdown shows matching users. Select a name to mention them.
@mentioned users receive a push notification and an in-app notification. They can click the notification to jump directly to the comment.
Click **Reply** under any existing comment to start a threaded conversation. Replies are nested under the parent comment and keep related discussion organized.
The parent comment author receives a notification when someone replies to their thread.
Hover over any comment to see the reaction button. Click it to add an emoji reaction. Use reactions for quick acknowledgments without cluttering the thread.
## How does the approval workflow work?
For cases and alerts that require formal approval:
1. Add one or more reviewers with the **Approver** role
2. Each approver reviews the case/alert and either **Approves** or **Rejects**
3. When all approvers have approved, the system flags the record as having all required approvals
4. If any approver rejects, they add feedback explaining what needs to change
You can check whether all approvals are in place before closing a case. The case detail view shows an "Approvals" section with each approver's status.
## How do notifications work?
Zenoo sends real-time notifications for collaboration events:
| Event | Who is notified | How |
| - | - | - |
| Added as a reviewer | The new reviewer | Push notification + in-app |
| @mentioned in a comment | The mentioned user | Push notification + in-app |
| Reply to your comment | The parent comment author | Push notification + in-app |
| Reviewer status changed | The case/alert owner | In-app |
Notifications link directly to the relevant case, alert, or comment — so recipients can jump into context immediately.
## What about system comments?
The system automatically generates comments for significant events:
* Status changes (e.g., "Case moved to Internal Review")
* Auto-triage results (e.g., "AI research completed: 92% false positive probability")
* Escalation events (e.g., "Case escalated to Jane Smith: SLA breach")
System comments appear in the timeline with a distinct style so you can distinguish them from human comments.
## What's next?
Use collaboration and approvals as part of the case resolution workflow.
Discuss alert findings with reviewers before taking action.
Monitor deadlines while waiting for reviewer responses.
Every comment, reviewer action, and approval is logged.
# How Do I Manage Entities?
Source: https://docs.zenoo.com/guides/case-management/manage-entities
Navigate the UBO tree, view entity verification status, add new entities to a case, and manage cross-case entity history.
# How Do I Manage Entities?
Entities are the people and companies linked to a case. A company onboarding case might have a dozen entities — the company itself, its directors, UBOs, and shareholders. This guide explains how to navigate, manage, and track verification status for all of them.
## What you'll learn
* How to read the entity panel and UBO tree
* What each entity role means for required checks
* How to view an entity's verification status
* How person cases work for individual entities
* How cross-case entity history works
## The entity panel
When you open a case, the entity panel shows all linked entities in two views:
The tree view shows the ownership and control structure:
* **Root node** — the primary entity (company)
* **Branch nodes** — directors, UBOs, and shareholders, connected by lines showing their relationship
* **Ownership percentages** — displayed next to UBO nodes
* **Status indicators** — colored dots showing each entity's verification status
Click any node to expand its detail panel showing checks, documents, and alerts.
The list view shows all entities in a flat table:
* **Name** — entity name
* **Role** — Primary, UBO, Director, Shareholder
* **Type** — Company or Individual
* **Verification Status** — Pending, In Progress, Verified, Failed
* **Risk Rating** — High, Medium, Low
* **Ownership %** — for UBOs and shareholders
* **EDD Required** — whether Enhanced Due Diligence is flagged
Click any row to see that entity's checks, documents, and alerts.
## What does each entity role require?
The entity's role determines which checks are automatically created:
| Check | Primary (Company) | UBO | Director | Shareholder |
| - | - | - | - | - |
| PEP/Sanctions Screening | Required | Required | Required | Required |
| Adverse Media | Required | Required | Required | -- |
| Identity Verification | -- | Required | Required | Required |
| Address Verification | -- | Required | -- | -- |
| Source of Wealth | -- | If High Risk | If High Risk | -- |
If a UBO or director is flagged as high risk during screening, additional checks (Source of Wealth, Enhanced Due Diligence) are automatically added to their requirements.
## How do I view an entity's verification status?
Click an entity in the tree or list view to open its detail panel. The panel shows:
* **Overall status** — a summary badge: Pending (grey), In Progress (blue), Verified (green), Failed (red), Expired (orange)
* **Check progress** — a breakdown of each check type with its current status
* **Document progress** — which documents have been received and which are still outstanding
* **Alerts** — any open alerts related to this specific entity
The overall status is calculated automatically:
* **Verified** — all required checks are Complete with a Pass result
* **Failed** — any required check has a Fail result that has not been resolved
* **In Progress** — at least one check is running or pending
* **Expired** — a previously verified entity whose checks have passed their validity period
## What are person cases?
When a company case includes individual entities (UBOs, directors, shareholders), the system automatically creates **person cases** for each one. Person cases:
* Track the individual's verification independently
* Can be shared across multiple company cases (if the same person appears in multiple companies)
* Are linked back to the company case via a relationship field
This means if John Smith is a director at two companies, he has one person case with his identity verification and screening — shared by both company cases.
## How does cross-case entity history work?
Zenoo tracks verification at the entity level, not just the case level. This means:
* **Reused checks** — if an entity was verified recently (within the validity period), new cases can reuse those check results instead of running them again. Reused checks show a "Reused" badge with the source case name.
* **Entity-level status** — each Account and Contact in Salesforce has an overall verification status that reflects their latest checks across all cases.
* **Verification history** — click "View History" on an entity to see all past checks across all cases, with dates, results, and validity periods.
Check reuse saves time and provider costs. A recently verified UBO joining a new company case will have their identity and screening checks automatically reused if they are still within the validity window.
## What's next?
Manage check requirements, trigger re-runs, and waive checks.
See how entity risk factors feed into the overall case risk.
Investigate alerts generated by entity checks.
Close the case once all entities are verified.
# How Do I Read the Audit Trail?
Source: https://docs.zenoo.com/guides/case-management/read-audit-trail
View immutable audit logs for every action in Case Management — filter by event type, actor, and date, and export for regulatory reporting.
# How Do I Read the Audit Trail?
Every action in Zenoo Case Management is logged immutably. The audit trail provides a complete record of who did what, when, and why — essential for regulatory examinations, internal compliance reviews, and dispute resolution.
## What you'll learn
* Where to find the audit trail
* What events are logged
* How to filter and search audit entries
* How to export data for regulatory reporting
* What the maker-checker audit pattern looks like
## Where is the audit trail?
The audit trail is accessible in two places:
1. **On a specific case or alert** — scroll to the **Audit Trail** section in the detail view to see all events for that record
2. **Global audit trail** — available from the main navigation for compliance officers and administrators who need to search across all records
## What events are logged?
The audit trail captures 32 event types across seven categories:
| Event | When it fires |
| - | - |
| **Case Created** | A new case is created (manually or from a verification flow) |
| **Case Status Change** | Case status transitions (New to In Progress, In Progress to Closed, etc.) |
| **Case Assignment** | Case is assigned or reassigned to an analyst |
| **Case Escalation** | Case is escalated to a manager or senior reviewer |
| **Case Closure** | Case is closed with resolution notes |
| Event | When it fires |
| - | - |
| **Alert Created** | A new alert is generated from a check result |
| **Alert Acknowledged** | An analyst acknowledges an open alert |
| **Alert Resolved** | An alert is resolved with an action and notes |
| **Alert False Positive** | An alert is marked as a false positive |
| **Alert Escalated** | An alert is escalated to a senior reviewer |
| **Alert Assignment** | An alert is assigned to a specific analyst |
| **Alert Auto-Disposition** | An alert is auto-resolved by the AI system |
| Event | When it fires |
| - | - |
| **Risk Assessment Created** | A new risk assessment is generated |
| **Risk Score Calculated** | Dimension scores are calculated |
| **Risk Tier Override** | An analyst overrides the calculated risk tier |
| **Risk Assessment Approved** | A risk assessment is formally approved |
| Event | When it fires |
| - | - |
| **Check Status Change** | A check transitions between statuses |
| **Check Waived** | A check is waived with a documented reason |
| **Check Rejected** | A check result is rejected |
| **Check Completed** | A check finishes with a result |
| **Document Uploaded** | A document is uploaded for a requirement |
| **Document Reviewed** | A document is reviewed and accepted or declined |
| Event | When it fires |
| - | - |
| **Reviewer Added** | A reviewer is added to a case or alert |
| **Reviewer Removed** | A reviewer is removed |
| **Reviewer Response** | A reviewer approves or rejects |
| **Comment Added** | A comment is posted on a case, alert, or check |
| Event | When it fires |
| - | - |
| **Entity Added** | An entity is added to a case |
| **Entity Removed** | An entity is removed from a case |
| **Auto Escalation** | A case is auto-escalated due to SLA breach |
| **SLA Breach** | A case breaches its SLA deadline |
| **Bulk Operation** | A bulk action is performed (e.g., bulk alert assignment) |
| **AI Analysis** | AI research is run on an alert |
## How do I filter audit entries?
The audit trail panel provides filters to narrow your search:
Select one or more event types from the dropdown (e.g., show only "Case Escalation" and "Risk Tier Override" events).
Set a start and end date to view events within a specific period. Useful for regulatory examinations that cover a defined timeframe.
Search for events performed by a specific user. The audit trail stores both the user ID and their name at the time of the event — so even if a user is deactivated, their name is preserved.
Use the search bar to find events containing specific text in their description, old value, new value, or reason fields.
## What does an audit entry show?
Each audit entry contains:
| Field | Description |
| - | - |
| **Timestamp** | When the event occurred (date and time to the second) |
| **Event type** | The category and specific event (e.g., "Alert Resolved") |
| **Actor** | Who performed the action (name and user ID) |
| **Record** | Which case, alert, or check the event relates to |
| **Description** | Human-readable description of what happened |
| **Old value** | The previous state (e.g., "Open") |
| **New value** | The new state (e.g., "Resolved") |
| **Reason** | The justification provided (e.g., resolution notes, override reason) |
| **Severity** | Info, Warning, or Critical |
Audit entries are immutable. Once created, they cannot be edited or deleted — not even by administrators. This is enforced by a validation rule at the database level.
## How do I export for regulatory reporting?
Click the **Export CSV** button at the top of the audit trail panel. The export includes:
* All visible entries (respecting your current filters)
* All fields in a flat CSV format
* Timestamps in ISO 8601 format for easy processing
* UTF-8 encoding
The CSV can be provided directly to regulatory examiners or imported into your organization's compliance reporting system.
Before a regulatory examination, use date range filters to export only the relevant period. Include all event types unless the examiner requests specific categories.
## What is the maker-checker pattern?
The audit trail supports the **four-eyes principle** (maker-checker) used in regulated environments:
* **Maker** — the analyst who performs an action (e.g., resolves an alert, overrides a risk tier)
* **Checker** — the approver who validates the action (e.g., a senior reviewer who approves the case)
The audit trail links these events with approved-by and approved-date fields, providing a clear chain of accountability that satisfies regulatory requirements for dual authorization.
## What's next?
See how case lifecycle events appear in the audit trail.
SLA breaches and auto-escalations are logged for review.
Risk overrides are fully audited.
Comments and reviewer actions appear in the audit trail.
# How Do I Resolve a Case?
Source: https://docs.zenoo.com/guides/case-management/resolve-a-case
Walk through the full case lifecycle in Zenoo Case Management — from a case landing in your queue to closing it with all alerts resolved.
# How Do I Resolve a Case?
A case represents a full compliance investigation. This guide walks through the typical lifecycle of a case from the moment it appears in your queue to when you close it. Along the way, you will review alerts, request information, collaborate with colleagues, and document your decisions.
## What you'll learn
* How a case arrives in your queue
* How to move a case through each status
* When and how to escalate
* How to request client input
* What is required before you can close a case
## Case lifecycle overview
```mermaid theme={null}
stateDiagram-v2
[*] --> New: Case created
New --> InProgress: Analyst opens case
InProgress --> ClientInput: Need customer info
InProgress --> InternalReview: Need approval
InProgress --> Closed: All resolved
ClientInput --> InProgress: Info received
InternalReview --> InProgress: Returned for rework
InternalReview --> Closed: Approved
Closed --> [*]
```
## Walk through each stage
When a verification flow generates results that need review, a case is created with status **New**. The case appears in the case queue, showing:
* **Case name** — auto-generated reference number
* **Type** — Onboarding, Review, or Perpetual
* **Priority** — Critical, High, Medium, or Low
* **Primary entity** — the company or person under investigation
* **Open alerts count** — how many alerts need attention
* **SLA due date** — when the case must be resolved
Cases are sorted by priority. High-priority cases with approaching SLA deadlines appear at the top.
Click the case to open it. The case detail view shows:
* **Header** — case reference, type, status, priority, SLA countdown
* **Entity panel** — all entities linked to this case (company, directors, UBOs, shareholders) with their verification statuses
* **Alert panel** — all alerts on this case, grouped by entity
* **Risk panel** — the current risk assessment with 4-dimension scores
* **Activity timeline** — comments, status changes, and audit events
Opening the case automatically transitions it to **In Progress**, signaling to your team that someone is working on it.
Work through the alerts on the case. For each alert:
1. Read the alert details and screening hit data
2. Check the AI research results if available
3. Investigate using the entity context and related documents
4. Choose a resolution: Approve, Decline, Escalate, or Request Document
See [How Do I Review Alerts?](/guides/case-management/review-alerts) for the detailed alert review process.
Start with high-priority alerts and sanctions hits. Resolving the most critical issues first gives you a clearer picture of the overall case risk.
If you need additional information or documents from the customer, change the case status to **Client Input**. This:
* Pauses the SLA clock (the customer's response time is not counted against your SLA)
* Signals to your team that the case is waiting on external input
* Can trigger an email notification to the customer (if configured)
When the customer responds, move the case back to **In Progress** to resume your review.
If a case requires approval from a senior reviewer or compliance officer, move it to **Internal Review**:
1. Click **Escalate** on the case header
2. Select the escalation target (your manager or a designated senior reviewer)
3. Write an escalation note explaining why the case needs senior review
4. The case status changes to **Internal Review** and the escalation target receives a notification
The senior reviewer can:
* Return the case to you (back to **In Progress**) with feedback
* Approve the case and close it
* Add their own review notes and comments
Cases that breach their SLA deadline can be auto-escalated to your manager if this feature is enabled. See [Track SLA Compliance](/guides/case-management/track-sla).
Once all alerts are resolved and you are satisfied with the investigation, close the case:
1. Verify the open alert count is **0** — you cannot close a case with unresolved alerts
2. Review the risk assessment and confirm the risk tier is accurate
3. Click **Close Case**
4. Write resolution notes summarizing your findings and the overall decision
5. The case status changes to **Closed** and a completion timestamp is recorded
Closure is final. Once a case is closed, its status cannot be changed. Make sure all alerts are resolved and your resolution notes are complete before closing.
## How does risk get recalculated?
Each time you resolve an alert, the system recalculates the case's risk score. The risk score reflects:
* The severity and count of remaining open alerts
* The most recent approved risk assessment (if one exists)
* The alert categories present on the case
After closing all alerts, the final risk tier (High, Medium, or Low) is locked in. See [How Do I Read Risk Scores?](/guides/case-management/understand-risk-scores) for details.
## What's next?
Detailed guide to investigating and resolving individual alerts.
Work with the entity hierarchy during your investigation.
Add reviewers and discuss the case with colleagues.
Verify that every action on the case is recorded.
# How Do I Review Alerts?
Source: https://docs.zenoo.com/guides/case-management/review-alerts
Walk through the alert workspace — filter your queue, investigate screening hits, use AI insights, and take action.
# How Do I Review Alerts?
Alerts are the primary work items in Case Management. Each alert represents a compliance issue that needs human judgment — a sanctions match, a PEP flag, an identity discrepancy, or a document problem. This guide walks through the alert workspace and how to process alerts efficiently.
## What you'll learn
* How to navigate the alert workspace
* How to filter and sort your alert queue
* How to investigate an individual alert
* What information the detail panel shows
* How to take action on an alert
## The alert workspace
The alert workspace is split into two panels:
* **Left panel: Alert queue** — a prioritized list of alerts assigned to you or your team, with filters and sort options
* **Right panel: Alert detail** — the full context for the selected alert, including screening hit data, AI research, comments, and action buttons
## How do I find the alerts I need?
From the main navigation, click **Alerts** to open the alert workspace. By default, you see alerts assigned to you, sorted by priority score (highest first).
Use the filter bar at the top of the queue to narrow your view:
* **Type** — Screening, Identity, Document, Company, Financial
* **Category** — PEP Match, Sanctions Hit, Adverse Media, Name Mismatch, etc.
* **Status** — Open, Acknowledged, Resolved, False Positive
* **Priority** — Critical, High, Medium, Low
* **SLA Status** — On Track, At Risk, Breached
Click the sort dropdown to order your queue by:
* **Priority score** (default) — a composite score combining SLA urgency, risk tier, category severity, match confidence, and case risk
* **SLA due date** — nearest deadline first
* **Created date** — newest or oldest first
* **Match score** — highest confidence matches first
## How do I investigate an alert?
Click any alert in the queue to open its detail panel. The detail panel is organized into sections:
The top section shows at a glance:
* **Alert title** — a human-readable description (e.g., "PEP Match - John Smith")
* **Category badge** — color-coded by type (red for sanctions, orange for PEP, blue for adverse media)
* **Priority badge** — Critical, High, Medium, or Low
* **SLA indicator** — time remaining until the deadline, color-coded (green/yellow/red)
* **Match score** — confidence percentage for screening matches
* **Assigned to** — the analyst responsible
For screening alerts, this section shows the match details:
* **Matched name** — the name in the database
* **Match score** — how closely the names match
* **Source database** — which list or provider flagged the match
* **Category** — PEP, Sanctions, Adverse Media, or Watchlist
* **Country** — countries associated with the matched entity
* **Additional details** — positions held (for PEPs), list names (for sanctions), article summaries (for adverse media)
If AI research has been run on this alert, you will see:
* **AI assessment** — the AI's analysis of whether this is a true match
* **Confidence score** — how confident the AI is in its assessment
* **False positive probability** — estimated likelihood this is a false positive
* **Recommended action** — what the AI suggests (approve, escalate, investigate further)
* **Sources** — references the AI used for its analysis
See [How Do I Use AI Research?](/guides/case-management/use-ai-research) for more detail.
Shows context about the entity this alert relates to:
* **Entity name and role** — e.g., "John Smith — Director"
* **Parent case** — link to the investigation case
* **Other alerts** — what other alerts exist for this entity
* **Check status** — where all checks stand for this entity
## How do I take action?
Once you have reviewed the alert details, click the **Resolve** button to open the resolution modal. Choose an action:
| Action | When to use | What happens |
| - | - | - |
| **Approve** | The match is a false positive or acceptable | Alert status changes to Resolved. Case open alert count decreases. |
| **Decline** | The match is confirmed and unacceptable | Alert status changes to Resolved with Decline action. May trigger case rejection. |
| **Escalate** | You need a senior reviewer's input | Alert is escalated. The parent case moves to Internal Review. A notification is sent to the escalation target. |
| **Request Document** | You need additional evidence from the customer | Alert stays open. A document request is created. The case may move to Client Input. |
| **Approve with Conditions** | Acceptable with additional requirements | Alert is resolved with conditions noted. May trigger EDD requirements. |
Every resolution action is logged in the audit trail with the analyst's name, timestamp, and any notes provided. This record is immutable and available for regulatory review.
## What about auto-triaged alerts?
Some alerts are processed by the AI auto-triage system before they reach your queue. These alerts have:
* An AI assessment already populated
* A false positive probability score
* A "QA Sample" flag if the alert was auto-resolved but selected for your quality review
Auto-resolved alerts with the QA Sample flag appear in your queue for spot-checking. Review them to verify the AI made the correct decision.
## What's next?
After resolving all alerts, close the case.
Get AI analysis to help validate screening matches.
Add reviewers and discuss alerts with colleagues.
Monitor deadlines across your alert queue.
# How Do I Run and Manage Checks?
Source: https://docs.zenoo.com/guides/case-management/run-checks
View required checks per entity, see check statuses, trigger re-runs, waive checks with a reason, and understand reused checks.
# How Do I Run and Manage Checks?
Checks are the verification tasks that run against each entity in a case. Some run automatically when a case is created. Others require manual action or document uploads. This guide covers how to view, manage, and take action on checks.
## What you'll learn
* How to view check requirements for an entity
* What each check status means
* How to trigger a manual re-run
* How to waive a check with a documented reason
* How reused checks work
* What happens when a check fails
## How do I view checks for an entity?
Open a case and click on any entity in the entity panel. The **Requirements** section shows all checks and documents for that entity in a grouped list:
| Section | What it shows |
| - | - |
| **Identity** | ID verification, biometric match, address verification |
| **Screening** | PEP/sanctions, adverse media, watchlist checks |
| **Company** | Registry lookup, credit check (for company entities) |
| **Financial** | Source of wealth, transaction monitoring |
| **EDD** | Enhanced due diligence checks (only shown when EDD is required) |
| **Documents** | Required document uploads (Certificate of Incorporation, proof of address, etc.) |
Each check shows:
* **Check name** — e.g., "PEP/Sanctions Screening"
* **Status badge** — color-coded (green for Complete, blue for In Progress, grey for Pending, red for Failed)
* **Result** — Pass, Refer, or Fail (once complete)
* **Provider** — which service ran the check (e.g., WorldCheck, Kyckr)
* **Date** — when the check was completed or last updated
## What does each status mean?
| Status | Color | Meaning | Available actions |
| - | - | - | - |
| **Pending** | Grey | Not yet started | Run, Waive |
| **Queued** | Blue | Waiting to be processed | View |
| **In Progress** | Blue | Currently executing | View |
| **Awaiting Client** | Orange | Customer needs to take action (e.g., complete identity verification at a URL) | View, Cancel |
| **Awaiting Document** | Orange | Waiting for a document upload | Upload, Waive |
| **Complete** | Green/Red | Finished with a result (Pass, Refer, or Fail) | View Results |
| **Failed** | Red | Technical failure after retries | Retry, Waive |
| **Waived** | Grey | Skipped with a documented reason | View reason |
| **Reused** | Purple | Result carried over from a valid previous check | View source |
| **Cancelled** | Grey | No longer needed | -- |
## How do I run a check manually?
Most API checks run automatically when a case is created. But you may need to trigger a manual run in these situations:
Open the entity's requirements panel and locate the check you want to run.
Click the **Run** button next to the check. This is only available for checks in **Pending** or **Failed** status.
The check moves to **In Progress**. For API checks, results typically arrive within 10-30 seconds. The status updates automatically — no need to refresh.
For checks that require customer action (like identity verification), the status changes to **Awaiting Client** and a verification URL is generated.
When the check completes, it shows a result:
* **Pass** — no issues found
* **Refer** — issues found that need review (an alert is generated)
* **Fail** — the check could not be completed successfully
If the result is Refer or Fail, an alert is automatically created and appears in the alert queue.
## How do I waive a check?
Sometimes a check is not applicable and should be skipped. For example, a source of wealth check may not be required for low-risk entities, or a document may be available through other means.
1. Click **Waive** next to the check
2. Enter a **reason** explaining why the check is being waived
3. Select the **reviewer** who approved the waiver (for audit purposes)
4. Click **Confirm**
Waivers are logged in the audit trail. Every waiver must have a documented reason. Compliance auditors will review waivers during regulatory examinations.
## How do reused checks work?
When a new case is created for an entity that was recently verified, the system checks for existing valid results:
* **Validity period** — each check type has a validity window (e.g., identity checks are valid for 365 days, screening for 90 days)
* **Reuse criteria** — the previous check must be Complete with a Pass result and within its validity period
* **Display** — reused checks show a purple "Reused" badge, the original check date, and a link to the source case
Reused checks save time and reduce costs by avoiding redundant provider API calls.
Reused checks are read-only. If you need a fresh result for a reused check, click **Run New Check** to create a new check that calls the provider again.
## What happens when a check fails?
Technical failures (provider timeout, API error, network issue) are handled automatically:
1. The system retries up to 3 times with increasing delays
2. If all retries fail, the check status changes to **Failed** with an error message
3. An API Failure alert may be generated depending on configuration
4. You can manually **Retry** the check or **Waive** it if the failure is not recoverable
## What's next?
See how checks fit into the entity verification hierarchy.
Investigate alerts generated by check results.
See how check outcomes affect risk assessments.
Verify that check executions and waivers are logged.
# How Do I Track SLA Compliance?
Source: https://docs.zenoo.com/guides/case-management/track-sla
Monitor SLA deadlines with visual indicators, understand warning and breach states, and configure auto-escalation.
# How Do I Track SLA Compliance?
Service Level Agreements (SLAs) ensure compliance work is completed on time. Zenoo tracks SLA deadlines on every case and provides visual indicators, automated warnings, and auto-escalation to help your team stay on track.
## What you'll learn
* How SLA deadlines are assigned to cases
* How to read SLA status indicators
* What happens when an SLA is at risk or breached
* How auto-escalation works
* How to monitor SLA performance across your team
## How are SLA deadlines assigned?
When a case is created, the system automatically calculates its SLA due date based on two factors:
| Factor | How it affects SLA |
| - | - |
| **Case type** | Onboarding, Review, and Perpetual cases may have different SLA windows |
| **Priority** | Critical and High priority cases get shorter SLA windows than Medium and Low |
These rules are configured in custom metadata. For example:
| Case Type | Priority | SLA Due Days | Warning Days Before |
| - | - | - | - |
| Onboarding | Critical | 3 days | 1 day |
| Onboarding | High | 7 days | 2 days |
| Onboarding | Medium | 14 days | 3 days |
| Onboarding | Low | 30 days | 5 days |
| Review | Critical | 5 days | 2 days |
| Review | High | 14 days | 3 days |
SLA clocks pause when a case is in **Client Input** status. The time spent waiting for the customer does not count against your SLA.
## How do I read SLA indicators?
SLA status is visible in three places: the case queue, the case header, and the alert queue. The indicator uses four states:
| Status | Color | Icon | Meaning |
| - | - | - | - |
| **On Track** | Green | Clock | Plenty of time remaining |
| **Warning** | Amber | Clock with exclamation | Due date is approaching (within the warning window) |
| **Critical** | Red | Clock with urgent | Due date is very close (within 24 hours) |
| **Breached** | Dark red | Clock with X | Past the due date |
The indicator also shows the **time remaining** (e.g., "3d 4h remaining") or **time overdue** (e.g., "2h overdue").
## What happens at each SLA stage?
The case is within its normal processing window. No special action needed. The SLA badge is green and shows remaining time.
The case has entered its warning window (e.g., 2 days before the SLA deadline). The system:
* Changes the SLA badge to amber
* Sends a warning email to the assigned analyst
* Bumps the case's priority score so it appears higher in the queue
When you see a warning indicator, prioritize that case. Resolving it before it breaches keeps your team's SLA metrics healthy.
The case is within 24 hours of its deadline. The SLA badge turns red. This is your last chance to resolve before a breach.
The case has passed its due date. The system:
* Changes the SLA badge to dark red with "Breached" text
* If auto-escalation is enabled, escalates the case to the analyst's manager
* Logs an SLA breach event in the audit trail
* Updates the SLA breach count in analytics
SLA breaches are visible in analytics dashboards and audit reports. Regulatory examiners may review breach rates as an indicator of your compliance program's effectiveness.
## How does auto-escalation work?
Auto-escalation is an optional feature that automatically escalates cases when their SLA breaches:
1. A nightly batch job checks all open cases for SLA status
2. For cases that have breached and have auto-escalation enabled:
* The system finds the assigned analyst's manager
* The case is escalated to the manager with a system-generated note: "Auto-escalated: SLA breach"
* The case status changes to Internal Review
* Both the analyst and manager receive email notifications
3. The escalation is logged in the audit trail as an **Auto Escalation** event
Auto-escalation settings are configured per case type and priority in the SLA configuration metadata.
## How do I monitor SLA across my team?
Team leads and compliance officers can view SLA metrics in the analytics dashboard:
* **SLA compliance rate** — percentage of cases resolved before their due date
* **Cases at risk** — count of cases currently in Warning or Critical status
* **Average resolution time** — how long cases take to close compared to their SLA window
* **Breach count** — how many cases breached their SLA in the current period
* **Breach trends** — whether SLA performance is improving or declining
## What's next?
Close cases before their SLA deadline.
Alerts have SLA indicators too — prioritize approaching deadlines.
Escalate to colleagues when you need help meeting a deadline.
SLA breaches and escalations are logged for regulatory review.
# How Do Cases, Entities, and Alerts Work?
Source: https://docs.zenoo.com/guides/case-management/understand-cases
Understand the mental model behind Zenoo Case Management — cases contain entities, entities have checks, and checks generate alerts.
# How Do Cases, Entities, and Alerts Work?
Before you start reviewing work in Case Management, it helps to understand how the data is structured. This guide explains the hierarchy of cases, entities, checks, and alerts — and how they relate to each other.
## What you'll learn
* What a case represents and the three case types
* What entities are and how they relate to cases
* How checks work and when they generate alerts
* The difference between alerts and checks
* How the hierarchy fits together visually
## The hierarchy at a glance
```mermaid theme={null}
flowchart TD
A[Case] --> B[Entity: Acme Corp]
A --> C[Entity: John Smith - Director]
A --> D[Entity: Jane Doe - UBO]
B --> E[Check: Company Registry]
B --> F[Check: PEP Screening]
C --> G[Check: Identity]
C --> H[Check: PEP Screening]
C --> I[Check: Adverse Media]
H --> J[Alert: PEP Match]
I --> K[Alert: Adverse Media Hit]
D --> L[Check: Identity]
D --> M[Check: PEP Screening]
D --> N[Check: Address]
```
## What is a case?
A **case** is an investigation. It represents a compliance review of a customer (individual or company) and everything connected to them. Every case has a type, a status, an assigned analyst, and a due date.
### Case types
| Type | When it is created | What it covers |
| - | - | - |
| **Onboarding** | A new customer starts verification | Initial KYC/KYB review of the customer and all related persons |
| **Review** | A periodic review is triggered | Re-assessment of an existing customer's risk profile and documentation |
| **Perpetual** | Ongoing monitoring detects a change | Investigation of new screening hits or risk changes for a previously approved customer |
### Case statuses
A case moves through these statuses during its lifecycle:
```mermaid theme={null}
stateDiagram-v2
[*] --> New
New --> InProgress: Analyst opens case
InProgress --> ClientInput: Waiting for customer
InProgress --> InternalReview: Sent for review
InProgress --> Closed: All resolved
ClientInput --> InProgress: Customer responds
ClientInput --> InternalReview: Escalated
ClientInput --> Closed: Resolved
InternalReview --> InProgress: Returned
InternalReview --> Closed: Approved
Closed --> [*]
```
A case cannot be closed while it has open alerts. All alerts must be resolved, marked as false positive, or dismissed before the case can move to Closed.
## What is an entity?
An **entity** is a person or company linked to a case. Every case has at least one entity (the primary subject). Company cases typically have many entities — the company itself plus its directors, UBOs, and shareholders.
### Entity roles
| Role | Description | Typical checks required |
| - | - | - |
| **Primary** | The main subject of the case (company or individual) | Screening, adverse media, identity (if individual) |
| **UBO** | Ultimate Beneficial Owner (25%+ ownership) | Identity, screening, address, adverse media, source of wealth (if high risk) |
| **Director** | Director or officer of the company | Identity, screening, adverse media, source of wealth (if high risk) |
| **Shareholder** | Shareholder without UBO-level ownership | Identity, screening |
Each entity has a **verification status** that summarizes where it stands: Pending, In Progress, Verified, Failed, or Expired.
## What is a check?
A **check** is a verification task that runs against an entity. Checks are automatically created based on the entity's role and the due diligence level (standard CDD or enhanced EDD). Some checks run automatically via API; others require manual action or document uploads.
### Check statuses
| Status | Meaning |
| - | - |
| **Pending** | Not yet started |
| **Queued** | Waiting to be executed by the system |
| **In Progress** | Currently running |
| **Awaiting Client** | Waiting for the customer to take action (e.g., complete identity verification) |
| **Awaiting Document** | Waiting for a document upload |
| **Complete** | Finished — result is Pass, Refer, or Fail |
| **Failed** | Technical failure after retries |
| **Waived** | Skipped with a documented reason |
| **Reused** | Result carried over from a previous valid check on the same entity |
| **Cancelled** | No longer needed |
## What is an alert?
An **alert** is a compliance issue that requires human review. Alerts are generated when checks find problems — a sanctions match, a PEP flag, a name mismatch, a document issue. Alerts are the primary work items for compliance analysts.
### Alert types and categories
| Type | Categories | Example |
| - | - | - |
| **Screening** | PEP Match, Sanctions Hit, Adverse Media | WorldCheck found a match against a sanctions list |
| **Identity** | Name Mismatch, DOB Mismatch | OCR-extracted name does not match submitted name |
| **Document** | Document Expired, Tampering Detected | Passport expiry date is in the past |
| **Company** | Registry Mismatch, Dissolved Company | Company is listed as dissolved in the registry |
| **Financial** | Unusual Transaction, Source of Wealth | Financial patterns that require review |
### The difference between checks and alerts
Think of **checks** as the tests and **alerts** as the failed results. A check always runs. An alert is only created when a check finds something that needs attention.
* A PEP screening **check** runs against WorldCheck. If it finds a match, it creates a PEP Match **alert**.
* An identity **check** extracts data from a document. If the name does not match, it creates a Name Mismatch **alert**.
* A company registry **check** looks up the company. If the company is dissolved, it creates a Dissolved Company **alert**.
## What's next?
Learn how to work through your alert queue.
Walk through the full case lifecycle from New to Closed.
Navigate the entity hierarchy and manage verification status.
Understand check requirements and how to manage them.
# How Do I Read Risk Scores?
Source: https://docs.zenoo.com/guides/case-management/understand-risk-scores
Understand the 4-dimension risk assessment in Zenoo — Customer, Geographic, Product/Service, and Transaction risk — and how to apply overrides.
# How Do I Read Risk Scores?
Every case in Zenoo has a risk assessment that scores the entity across four dimensions. Understanding these scores helps you prioritize your workload, apply the right level of due diligence, and justify your decisions to auditors.
## What you'll learn
* What the four risk dimensions are
* How scores and tiers are calculated
* How to read the risk assessment panel
* When and how to apply an override
* How risk scores change as you resolve alerts
## The four risk dimensions
Zenoo follows the FATF (Financial Action Task Force) methodology for risk assessment. Every entity is scored across four dimensions:
| Dimension | What it measures | Example risk factors |
| - | - | - |
| **Customer** | Who is this entity? | PEP status, sanctions matches, adverse media, complex ownership structures |
| **Geographic** | Where does this entity operate? | High-risk jurisdictions, FATF grey/black list countries, EU high-risk third countries |
| **Product/Service** | What products or services are involved? | Cash-intensive businesses, anonymous transactions, high-value products |
| **Transaction** | How does this entity transact? | Unusual patterns, cross-border flows, structuring, rapid movement of funds |
Each dimension gets:
* A **score** from 0 to 100
* A **tier**: High (70-100), Medium (40-69), or Low (0-39)
* A list of **risk factors** explaining what contributed to the score
## How is the overall risk calculated?
The overall risk tier uses a **highest-dimension-wins** approach:
| Scenario | Overall tier |
| - | - |
| Any dimension is High | **High** |
| No dimension is High, but any is Medium | **Medium** |
| All dimensions are Low | **Low** |
This is deliberately conservative. A single high-risk dimension (e.g., a PEP match pushing Customer risk to High) elevates the entire case to High risk, regardless of how low the other dimensions score.
## How do I read the risk panel?
The risk assessment panel appears on the case detail view. It shows:
At the top, a large badge shows the **overall tier** (High, Medium, or Low) with its color code:
* **High** — red badge
* **Medium** — amber badge
* **Low** — green badge
Next to it, the overall numeric score (0-100) provides more granularity.
Below the overall badge, four horizontal bars show each dimension:
* **Bar fill** — proportional to the score (0-100)
* **Tier label** — High, Medium, or Low
* **Score** — the numeric value
Click any dimension bar to expand its risk factors — the specific reasons driving that score.
Each dimension's expanded view shows a bulleted list of risk factors. For example, the Customer dimension might show:
* "PEP Match: Senior government official"
* "Adverse media: 3 articles related to fraud investigation"
* "Complex corporate structure with multiple layers"
These factors come from alert analysis, entity attributes, and country risk data.
Below the dimension breakdown, the panel shows recommended actions based on the risk tier:
* **High risk** — Enhanced Due Diligence required, senior reviewer approval needed, additional documentation required
* **Medium risk** — Standard review, consider additional checks
* **Low risk** — Standard review, routine processing
## How do I apply an override?
Sometimes you have context that the automated risk assessment does not capture. You can override the risk tier:
1. Click **Override** on the risk assessment panel
2. Select the new tier (High, Medium, or Low)
3. Write a **justification** explaining why you are overriding the calculated tier
4. Click **Apply Override**
The override is logged in the audit trail with your name, timestamp, and justification. The override tier takes precedence over the calculated tier for all downstream processing.
Overrides are visible to auditors. Always provide a clear, specific justification. "I know this person" is not sufficient. "Entity is a retired PEP with no current political connections, last held office 15 years ago" is.
## How do risk scores change during a case?
Risk scores are recalculated when:
* **An alert is resolved** — removing a high-severity alert can reduce the corresponding dimension score
* **A new assessment is created** — a fresh assessment using the current model rules replaces the previous one
* **An override is applied** — the override tier becomes the effective tier
As you resolve alerts on a case, you may see the risk score decrease as risk factors are cleared.
## What's next?
Alerts drive risk scores — resolving them changes the assessment.
Close the case once the risk tier is finalized.
AI research provides additional context for risk decisions.
Risk overrides are logged for regulatory review.
# How Do I Use AI Research?
Source: https://docs.zenoo.com/guides/case-management/use-ai-research
Get AI-powered analysis of screening matches with confidence scores, false positive probabilities, and recommended actions.
# How Do I Use AI Research?
Zenoo's AI research feature analyzes screening alerts using real-time web research and machine learning. It helps you determine whether a screening match is a true positive or a false positive, saving hours of manual investigation.
## What you'll learn
* How to trigger AI research on an alert
* What the AI insights panel shows
* How to interpret confidence scores and false positive probabilities
* How auto-triage works
* When to rely on AI and when to investigate manually
## How do I run AI research?
Navigate to your alert queue and click on a screening alert (PEP Match, Sanctions Hit, or Adverse Media).
In the alert detail panel, click the **AI Research** button. The system queues the alert for AI analysis. A spinner indicates processing is in progress.
AI research runs asynchronously. You can continue working on other alerts while it processes. You will receive a notification when the analysis is complete.
When analysis completes, the AI insights panel populates with the results. You can also access the results by returning to the alert later — they persist on the alert record.
## What does the AI insights panel show?
The AI insights panel is divided into several sections:
The AI's overall assessment of the screening match:
* **Match assessment** — a narrative explanation of whether the matched person in the database is likely the same person as the entity being verified
* **Key findings** — bullet points summarizing the most important evidence for or against a true match
* **Comparison points** — specific data points compared (name, date of birth, nationality, known addresses)
Two numeric scores help you gauge reliability:
* **AI Confidence** (0-100%) — how confident the AI is in its assessment. Higher means more certainty.
* **False Positive Probability** (0-100%) — the estimated likelihood that this match is a false positive. Higher means more likely to be a false positive.
| False Positive Probability | Interpretation |
| - | - |
| **90-100%** | Almost certainly a false positive — different person with similar name |
| **70-89%** | Likely false positive — some similarities but key differences |
| **40-69%** | Uncertain — needs manual investigation |
| **10-39%** | Likely true positive — significant similarities |
| **0-9%** | Almost certainly a true match — investigate thoroughly |
The AI suggests a resolution action:
* **Approve** — the AI believes this is a false positive and recommends clearing the alert
* **Escalate** — the AI is uncertain and recommends senior review
* **Investigate** — the AI found concerning signals that warrant deeper investigation
* **Decline** — the AI believes this is a true match with high confidence
AI recommendations are suggestions, not decisions. You are always responsible for the final determination. Use the AI research as one input alongside your own judgment and any additional evidence.
The AI cites its sources:
* **Web sources** — links to articles, company registries, government databases that were consulted
* **Database matches** — details from the screening provider's database entry
* **Entity data** — the submitted entity information used for comparison
Click any source to view the original content and verify the AI's interpretation.
## How does auto-triage work?
For high-volume teams, Zenoo can automatically triage new screening alerts:
1. When a new alert is created, the auto-triage system checks if it matches the configured criteria (alert type, category, status)
2. If eligible, AI research runs automatically — no manual trigger needed
3. After research completes, the auto-disposition engine evaluates the results
4. If the false positive probability exceeds the configured threshold (e.g., 95%), the alert is automatically resolved as a false positive
5. A percentage of auto-resolved alerts are flagged for **QA sampling** — they appear in your queue with a "QA Sample" badge for spot-checking
Auto-triage and auto-disposition are configured by your compliance team's administrator. Not all alert types are eligible, and thresholds vary by category. Sanctions alerts, for example, typically require manual review regardless of the AI's assessment.
## When should I investigate manually?
AI research is most effective for:
* **Common name matches** — where the AI can compare biographical details to distinguish between individuals
* **PEP matches** — where public information about the PEP's role and tenure is available
* **Adverse media** — where the AI can assess whether media articles refer to the same individual
Investigate manually when:
* The AI confidence is below 70%
* The alert involves sanctions (even high-confidence AI results on sanctions should be verified)
* The entity's risk tier is already High for other reasons
* The false positive probability is in the uncertain range (40-69%)
## What's next?
Apply AI insights during your alert review workflow.
See how AI findings contribute to risk assessments.
Share AI research findings with colleagues via comments.
AI research actions and auto-dispositions are logged for compliance.
# Check Orchestration
Source: https://docs.zenoo.com/guides/check-orchestration
How verification checks are auto-created, executed, and reused across cases in the Zenoo CLM platform.
# Check Orchestration
The check framework automatically creates, executes, and tracks verification requirements for each entity in a case. Checks are the system's way of verifying facts about a person or company through external providers.
## How checks are created
When a case entity is created (either through the API or as part of case creation), the orchestration service automatically:
1. Reads the verification type configuration for the entity type and role
2. Filters by due diligence level (CDD vs EDD)
3. Creates the appropriate records based on execution method
```mermaid theme={null}
flowchart TD
A[Entity added to case] --> B[Read Verification Type config]
B --> C{Execution method?}
C -->|API / Manual| D[Create Required Check]
C -->|Document / FreeText / Dropdown| E[Create Document Requirement]
C -->|Hybrid| F[Create Both with cross-links]
D --> G{Auto-execute?}
G -->|Yes| H[Execute via API adapter]
G -->|No| I[Status: Pending]
H --> J[Process result]
J -->|Pass| K[Check Complete]
J -->|Refer / Fail| L[Generate Alert]
```
## Check types and categories
| Category | Check Types | Execution |
| - | - | - |
| `Screening` | PEP Screening, Sanctions Screening, Adverse Media | API |
| `Identity` | Identity Verification, Biometric Verification | API |
| `Company` | Company Registry, Directors Verification, UBO Check | API |
| `Financial` | Credit Check, Source of Wealth | API/Manual |
| `EDD` | Enhanced Due Diligence checks (added when required) | Manual |
## Execution modes
Checks execute in one of three modes depending on the provider configuration:
### Individual mode
One API call per check. Used for simple, single-purpose verifications.
**Example:** Twilio phone verification -- one call verifies one phone number.
```mermaid theme={null}
sequenceDiagram
participant Z as Zenoo
participant P as Provider
Z->>P: executeCheck(check_1)
P-->>Z: Result
Z->>P: executeCheck(check_2)
P-->>Z: Result
```
### Category batch mode
One API call returns results for multiple check types. Used for providers that screen across multiple categories simultaneously.
**Example:** WorldCheck -- one call returns PEP, Sanctions, and Adverse Media results.
```mermaid theme={null}
sequenceDiagram
participant Z as Zenoo
participant P as WorldCheck
Z->>P: executeCategoryCheck([PEP, Sanctions, AdverseMedia])
P-->>Z: Results for all 3 checks
```
### Async mode
The check initiates a client-side process (e.g., document upload, selfie capture). Results arrive via webhook when the client completes the action.
**Example:** Zenoo Verify identity check -- generates a verification URL, client completes document capture, result arrives via callback.
```mermaid theme={null}
sequenceDiagram
participant Z as Zenoo
participant P as Provider
participant C as Client
Z->>P: initiateCheck()
P-->>Z: verification_url + token
Z-->>C: Redirect to verification URL
C->>P: Complete verification
P->>Z: Webhook callback with results
```
Checks in this mode have a configurable expiry. An hourly batch job marks expired `Awaiting Client` checks as `Failed`.
## Check status lifecycle
```mermaid theme={null}
stateDiagram-v2
[*] --> Pending
Pending --> Queued : Auto-execute queued
Pending --> In_Progress : Manual start
Pending --> Waived : Analyst waives
Queued --> In_Progress : Execution begins
In_Progress --> Complete : Result received
In_Progress --> Failed : Error or timeout
In_Progress --> Awaiting_Document : Hybrid, needs upload
In_Progress --> Awaiting_Client : Client interaction needed
Awaiting_Client --> Complete : Client completes
Awaiting_Client --> Failed : Expired
Awaiting_Document --> Complete : Document received
Complete --> [*]
Failed --> Retry_Scheduled : Retry queued
Retry_Scheduled --> In_Progress : Retry executes
Failed --> [*]
Waived --> [*]
```
| Status | Description |
| - | - |
| `Pending` | Created, awaiting execution or manual action |
| `Queued` | Scheduled for automatic execution |
| `In Progress` | Currently executing against an external provider |
| `Awaiting Document` | Hybrid check waiting for document upload |
| `Awaiting Client` | Waiting for client-side action (e.g., selfie capture) |
| `Complete` | Execution finished with a result |
| `Failed` | Execution error or timeout |
| `Waived` | Manually waived by an analyst with documented reason |
| `Cancelled` | Cancelled (e.g., case closed before execution) |
| `Reused` | Valid check from another case reused |
| `Retry Scheduled` | Failed check scheduled for automatic retry |
## Check results
Every completed check produces one of three results:
| Result | Meaning | Alert generated? |
| - | - | - |
| `Pass` | Verification passed, no issues | No |
| `Refer` | Potential issue, needs review | Yes |
| `Fail` | Verification failed | Yes |
## Check reuse (Verify Once, Reference Many)
When a new case creates checks for an entity that has already been verified, the system evaluates existing checks for reuse:
1. Look up completed, passing checks for the same entity and check type
2. Verify the check has not expired (based on validity period)
3. If valid, create a `Reused` check referencing the original
This avoids redundant provider calls while maintaining full audit provenance.
| Check Category | Default Validity |
| - | - |
| Identity | 365 days |
| Screening | 90 days |
| Company | 180 days |
| Financial | 365 days |
| EDD | 180 days |
## Alert generation
Checks that return `Refer` or `Fail` automatically generate alerts:
| Check Result | Alert Category | Alert Priority |
| - | - | - |
| PEP screening match | `PEP Match` | High |
| Sanctions match | `Sanctions Hit` | Critical |
| Adverse media hit | `Adverse Media` | Medium |
| Company dissolved | `API Failure` | High |
| Name mismatch (\< 70%) | `Name Mismatch` | High/Medium |
| Identity failure | `Name Mismatch` | Medium |
## Next steps
* [Alert Management](/guides/alert-management). How generated alerts are triaged and resolved.
* [Entity Lifecycle](/guides/entity-lifecycle). How entity roles drive check requirements.
* [Checks Data Model](/data-model/checks). Full field reference for check resources.
# Entity Lifecycle
Source: https://docs.zenoo.com/guides/entity-lifecycle
Understand entity types, roles, verification statuses, and access patterns in the Zenoo CLM platform.
# Entity Lifecycle
Entities represent the people and companies under investigation. Every case revolves around a primary entity and optionally includes related entities such as directors, UBOs, and shareholders.
## Entity types
Zenoo supports two entity types, each with distinct verification requirements:
| Type | Description | Verification scope |
| - | - | - |
| `company` | A legal entity (Ltd, LLC, GmbH, etc.) | Registry check, company screening, director/UBO screening |
| `person` | An individual (natural person) | Identity verification, biometrics, individual screening |
## Entity roles
When an entity is linked to a case, it is assigned a role that determines which checks are required:
| Role | Description | Check requirements |
| - | - | - |
| `Primary` | The main entity under investigation | Screening (required), Adverse Media (required), Identity (N/A for companies) |
| `UBO` | Ultimate Beneficial Owner (>25% ownership) | Identity (required), Screening (required), Address (required), Adverse Media (required) |
| `Director` | Company director or officer | Identity (required), Screening (required), Adverse Media (required) |
| `Shareholder` | Non-UBO shareholder | Identity (required), Screening (required) |
For UBOs and Directors, if the entity is assessed as High risk, Source of Wealth (SOW) checks are automatically added as an Enhanced Due Diligence (EDD) requirement.
## Verification status lifecycle
Each entity tracks its overall verification status independently of any specific case.
```mermaid theme={null}
stateDiagram-v2
[*] --> Not_Started
Not_Started --> In_Progress : First check begins
In_Progress --> Verified : All required checks pass
In_Progress --> Failed : Any required check fails
Verified --> Expired : Validity period exceeded
Expired --> In_Progress : Re-verification initiated
Failed --> In_Progress : Retry or new check
```
| Status | Description |
| - | - |
| `Not Started` | No checks have been executed for this entity |
| `In Progress` | At least one check is running or pending |
| `Verified` | All required checks completed with passing results |
| `Expired` | Previous verification has exceeded its validity period |
| `Failed` | One or more required checks returned a failing result |
## Entity creation
### Creating a company entity with related persons
```bash theme={null}
curl -X POST https://api.zenoo.com/v1/entities \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"type": "company",
"name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"directors": [
{ "first_name": "Jane", "last_name": "Smith", "role": "Director" }
],
"ubos": [
{ "first_name": "John", "last_name": "Doe", "ownership_percentage": 75, "role": "UBO" }
]
}'
```
When you create a company entity with directors and UBOs, Zenoo automatically:
1. Creates individual person entity records for each director and UBO
2. Links them to the parent company entity with the correct role
3. Returns all entity tokens in the response
### Creating a standalone person entity
```bash theme={null}
curl -X POST https://api.zenoo.com/v1/entities \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"type": "person",
"first_name": "Jane",
"last_name": "Smith",
"date_of_birth": "1980-05-20",
"nationality": "GB",
"country": "GB"
}'
```
## Cross-case entity reuse
Entities persist across cases. When a person entity (e.g., a UBO) appears in a new case, Zenoo checks for existing valid checks through the "Verify Once, Reference Many" system:
1. The system looks up previous passing checks for the same entity
2. If a valid, non-expired check exists, a `Reused` check is created instead of a new one
3. The reused check references the original check and source case
This prevents redundant verification while preserving full provenance.
### Check validity periods
| Check Category | Default Validity |
| - | - |
| Identity | 365 days |
| Screening | 90 days |
| Company | 180 days |
| Financial | 365 days |
| EDD | 180 days |
## Access patterns
### Entity-centric access
Retrieve all cases and checks for a specific entity:
```bash theme={null}
# Get entity with all related cases
curl -X GET "https://api.zenoo.com/v1/entities/ent_abc123?include=cases,checks" \
-H "Authorization: Bearer your-api-key"
```
### Case-centric access
Retrieve all entities linked to a specific case:
```bash theme={null}
# Get case with all entities
curl -X GET "https://api.zenoo.com/v1/cases/cas_xyz789/entities" \
-H "Authorization: Bearer your-api-key"
```
## Next steps
* [Case Lifecycle](/guides/case-lifecycle). How cases flow from creation to closure.
* [Check Orchestration](/guides/check-orchestration). How checks are auto-created for entity roles.
* [Entities Data Model](/data-model/entities). Full field reference for entity resources.
# Error Handling
Source: https://docs.zenoo.com/guides/error-handling
Handle API errors, implement retry strategies, and understand Zenoo's internal error classification.
# Error Handling
## HTTP Status Codes
| Status | Meaning | Your Action |
| - | - | - |
| `200` | Success | Process the response |
| `204` | No Content (pull endpoint) | Results still processing. Retry later. |
| `400` | Bad Request | Fix the request payload and resubmit |
| `401` | Unauthorized | Check your `X-API-KEY` header |
| `403` | Forbidden | Your API key does not have access to this project |
| `404` | Not Found | Invalid project hash, endpoint path, or expired pull token |
| `429` | Rate Limited | Back off and respect the `Retry-After` header |
| `500` | Internal Server Error | Retry with exponential backoff |
| `503` | Service Unavailable | Retry after 30 seconds |
## Error Response Format
All errors return a consistent JSON structure:
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Missing required field: company_name",
"request_id": "req-a1b2c3d4"
}
```
| Field | Description |
| - | - |
| `error` | Machine-readable error code. Use this for programmatic handling. |
| `message` | Human-readable description. Log it, but do not display to end users. |
| `request_id` | Unique identifier for the request. Include this in all support requests. |
Always log `request_id`. It allows Zenoo support to trace the exact request through internal systems.
## Error Codes
| Error Code | HTTP Status | Description |
| - | - | - |
| `VALIDATION_ERROR` | 400 | Missing or invalid required fields |
| `INVALID_JSON` | 400 | Request body is not valid JSON |
| `UNAUTHORIZED` | 401 | Missing or invalid API key |
| `FORBIDDEN` | 403 | API key lacks access to this project |
| `NOT_FOUND` | 404 | Invalid project hash or endpoint path |
| `TOKEN_EXPIRED` | 404 | Pull token has expired (30-day lifetime) |
| `RATE_LIMITED` | 429 | Too many requests. Check `Retry-After` header. |
| `INTERNAL_ERROR` | 500 | Unexpected server error |
| `PROVIDER_ERROR` | 502 | Upstream provider (registry, screening) returned an error |
| `SERVICE_UNAVAILABLE` | 503 | Service temporarily unavailable |
## Retry Strategy
### Exponential Backoff
For transient errors (5xx, timeouts), retry with increasing delays:
```
Attempt 1: Immediate
Attempt 2: Wait 5 seconds
Attempt 3: Wait 25 seconds
Attempt 4: Wait 60 seconds
Maximum: 4 attempts
```
```javascript retry-client.js theme={null}
async function callWithRetry(fn, maxRetries = 4) {
const delays = [0, 5000, 25000, 60000];
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fn();
if (response.status === 429) {
const retryAfter = response.headers.get("Retry-After");
const delay = retryAfter
? parseInt(retryAfter) * 1000
: delays[attempt] * 2;
await sleep(delay);
continue;
}
if (response.status >= 500) {
if (attempt < maxRetries - 1) {
await sleep(delays[attempt]);
continue;
}
}
return response;
} catch (error) {
if (attempt === maxRetries - 1) throw error;
await sleep(delays[attempt]);
}
}
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
```
```python retry_client.py theme={null}
import time
import requests
from requests.exceptions import RequestException
def call_with_retry(fn, max_retries=4):
delays = [0, 5, 25, 60]
for attempt in range(max_retries):
try:
response = fn()
if response.status_code == 429:
retry_after = response.headers.get('Retry-After')
delay = int(retry_after) if retry_after else delays[attempt] * 2
time.sleep(delay)
continue
if response.status_code >= 500:
if attempt < max_retries - 1:
time.sleep(delays[attempt])
continue
return response
except RequestException:
if attempt == max_retries - 1:
raise
time.sleep(delays[attempt])
```
### When NOT to Retry
| Error | Why |
| - | - |
| `400` Bad Request | Your request is malformed. Fix the payload first. |
| `401` Unauthorized | Your API key is wrong. Retrying will not fix it. |
| `403` Forbidden | Your key lacks permissions. Contact Zenoo support. |
| `404` Not Found | The resource does not exist. Verify your endpoint URL and project hash. |
Never retry 4xx errors automatically. They indicate a problem with your request, not a transient server issue.
## Rate Limiting
When you exceed your project's rate limit, you receive a `429` response. The `Retry-After` header tells you how many seconds to wait. Always use this value instead of guessing.
```json theme={null}
{
"error": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 60 seconds.",
"request_id": "req-a1b2c3d4"
}
```
**Handling rate limits:**
1. Read the `Retry-After` header value.
2. Pause requests for that duration.
3. Resume. If you hit the limit again, increase your backoff.
For high-volume integrations, implement a request queue. Smooth out bursts rather than sending requests as fast as possible. Monitor your request rate and contact Zenoo if you need higher limits.
## Zenoo Internal Error Classification
Zenoo classifies errors from upstream providers into three categories. This determines automatic retry behavior on the server side.
| Category | Examples | Retry Behavior |
| - | - | - |
| **Transient** | Timeouts, 5xx from providers | Auto-retry with exponential backoff |
| **Permanent** | 4xx, invalid data, entity not found | No retry. Check fails immediately. |
| **Rate Limited** | 429 from providers | Extended backoff (2x multiplier) |
### Automatic Retry Schedule
For async flows, Zenoo automatically retries transient provider failures on your behalf:
| Retry | Delay | Cumulative Wait |
| - | - | - |
| 1st | 5 minutes | 5 minutes |
| 2nd | 25 minutes | 30 minutes |
| 3rd | 125 minutes | \~2.5 hours |
Maximum delay is capped at 240 minutes (4 hours). Rate-limited errors use a 2x multiplier on these delays.
After all retries are exhausted:
* The check is marked as `Failed`.
* A `check.failed` webhook is sent to your endpoint.
* An API Failure alert is created in the case management system.
You do not need to implement retry logic for provider failures. Zenoo handles it automatically.
## Circuit Breaker
Zenoo protects upstream providers with a circuit breaker pattern:
```mermaid theme={null}
stateDiagram-v2
[*] --> Closed
Closed --> Open: failures > threshold
Open --> HalfOpen: reset timeout
HalfOpen --> Closed: success
HalfOpen --> Open: failure
```
| State | Behavior |
| - | - |
| **Closed** | Normal operation. Requests flow through to the provider. |
| **Open** | Provider is down. Requests fail fast and are queued for retry. |
| **Half-Open** | Recovery test. A single request is sent to check if the provider is back. |
This is transparent to your integration. You do not need to implement any special handling. When a provider's circuit breaker opens:
* Checks for that provider are queued automatically.
* Other providers continue operating normally.
* You receive results via webhook once the provider recovers.
From your perspective, the verification simply takes longer to complete.
## Timeout Handling
### Sync Requests
If your `X-SYNC-TIMEOUT` is reached before all checks complete:
* The response may contain partial results.
* A pull token is included in the response headers for retrieving full results later.
* Consider increasing the timeout or switching to async mode.
### Async Requests
The `/init` endpoint responds immediately with tokens, so connection timeouts are rare. If you experience them:
* Check your network connectivity.
* Verify the base URL and project hash.
* Contact Zenoo support if the issue persists.
## Monitoring Recommendations
1. **Track error rates** by endpoint and HTTP status code.
2. **Alert on 5xx spikes.** Sustained 5xx errors may indicate a provider outage or Zenoo infrastructure issue.
3. **Monitor webhook delivery.** Track delivery failures and retry rates.
4. **Log `request_id` for every API call.** This is essential for debugging with Zenoo support.
5. **Track response times.** Latency increases may indicate provider degradation.
6. **Monitor rate limit proximity.** Alert when you reach 80% of your limit to avoid hitting it during traffic spikes.
## Next Steps
* [Webhooks](/guides/webhooks) -- Handle webhook delivery failures and retries
* [Idempotency](/guides/idempotency) -- Prevent duplicate verifications
* [Result Delivery](/guides/result-delivery) -- Choose between push, pull, and ping+pull
* [Testing & Sandbox](/testing/staging-environment) -- Test error scenarios in staging
# Idempotency
Source: https://docs.zenoo.com/guides/idempotency
Prevent duplicate verifications using the external_reference field for idempotent requests.
# Idempotency
Zenoo supports idempotent requests through the `external_reference` field. This prevents duplicate verifications caused by retries, user double-clicks, network failures, or asynchronous workflow replays.
## How It Works
Include an `external_reference` in any verification request. If you submit the same `external_reference` twice:
1. The second request returns the existing case's tokens.
2. No duplicate verification is created.
3. No additional charges apply.
The `external_reference` is scoped to your project. Two different projects can use the same reference value without conflict.
## Company Verification Example
```bash theme={null}
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"external_reference": "ONBOARD-2026-0042"
}'
```
## Person Verification Example
```bash theme={null}
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/kyc/init" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-d '{
"first_name": "Jane",
"last_name": "Smith",
"date_of_birth": "1990-06-15",
"country": "GB",
"external_reference": "PERSON-2026-0789"
}'
```
## Duplicate Detection Behavior
When Zenoo receives a request with an `external_reference` that already exists in your project:
```
First request: external_reference: "ONBOARD-2026-0042"
-> Creates new case, returns tokens A
Second request: external_reference: "ONBOARD-2026-0042"
-> Returns tokens A (same case, no new verification)
```
The response to the second request is identical to the first. You get back the same `pull` and `start` tokens pointing to the original case.
### What Happens to the Request Body
The second request's body fields (company name, country, etc.) are ignored. Zenoo returns the existing case based solely on the `external_reference` match. If you need to run a new verification with different data for the same entity, use a different `external_reference`.
## When to Use
Always, in production. The `external_reference` field protects against accidental duplicates with no downside.
The `external_reference` field protects against:
* **Network retries.** If your HTTP client retries a timed-out request, you do not get charged twice.
* **User double-clicks.** If a user clicks "Verify" twice, only one verification runs.
* **Workflow replays.** If your orchestration system replays a failed step, the verification is not duplicated.
* **Distributed systems.** If two instances of your service submit the same verification concurrently, one case is created.
## Best Practices
**Use your internal identifiers.** Map `external_reference` to something unique in your system:
| Your System | Example Reference |
| - | - |
| Loan application | `APP-2026-0042` |
| Customer onboarding | `ONBOARD-2026-1234` |
| Periodic review | `REVIEW-2026-Q1-0042` |
| Person re-verification | `PERSON-REVERIFY-2026-0042` |
**Include it in every production request.** There is no downside and it prevents accidental duplicates.
Make references deterministic. Derive them from your data (application ID, customer ID + date) rather than generating random UUIDs. This ensures the same logical operation always produces the same reference.
```javascript reference-generator.js theme={null}
// Good: deterministic, derived from your data
const reference = `APP-${applicationId}`;
// Also good: includes a scope
const reference = `REVIEW-${customerId}-${quarter}`;
// Bad: random UUID defeats the purpose
const reference = crypto.randomUUID();
```
**Include the scope in the reference.** If the same entity can have multiple verification types (initial onboarding, periodic review, re-verification), include the type in the reference to avoid collisions:
```
ONBOARD-CUST-1234 // initial onboarding
REVIEW-CUST-1234-2026Q1 // quarterly review
REVERIFY-CUST-1234-EDD // EDD re-verification
```
## Webhook Deduplication
Idempotency also matters on the receiving side. Zenoo may deliver the same webhook event more than once.
Use `journey_id` combined with `event_type` as a deduplication key:
```javascript server.js theme={null}
app.post("/webhooks/zenoo", async (req, res) => {
const { journey_id, event_type } = req.body;
const dedupeKey = `${journey_id}:${event_type}`;
const existing = await db.webhookEvents.findOne({ dedupeKey });
if (existing) {
return res.status(200).json({ received: true, duplicate: true });
}
await db.webhookEvents.create({ dedupeKey, payload: req.body });
await queue.enqueue("process-webhook", req.body);
res.status(200).json({ received: true });
});
```
Always return `200` for duplicates. Returning an error triggers unnecessary retries.
## Testing Idempotency
Verify that duplicate requests return the same case:
```bash theme={null}
# First request
curl -X POST "https://instance.staging.onboardapp.io/api/gateway/execute/{hash}/api" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{
"company_name": "Test Idempotent Ltd",
"registration_number": "TEST-IDEM-001",
"country": "GB",
"external_reference": "idem-test-001"
}'
# Save the case_reference and tokens from the response
# Second request (identical external_reference)
curl -X POST "https://instance.staging.onboardapp.io/api/gateway/execute/{hash}/api" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{
"company_name": "Test Idempotent Ltd",
"registration_number": "TEST-IDEM-001",
"country": "GB",
"external_reference": "idem-test-001"
}'
# Verify: same case_reference returned, same tokens, no duplicate case
```
You should receive the same `case_reference` and tokens in both responses. No new verification is created.
## Next Steps
* [Error Handling](/guides/error-handling) -- Retry strategies and backoff patterns
* [Webhooks](/guides/webhooks) -- Webhook deduplication in detail
* [Result Delivery](/guides/result-delivery) -- Retrieving verification results
# Ongoing Monitoring
Source: https://docs.zenoo.com/guides/ongoing-monitoring
Implement periodic re-screening, check expiry management, and triggered re-verification for ongoing compliance.
# Ongoing Monitoring
Verification is not a one-time event. Regulatory requirements mandate periodic re-screening, check expiry management, and triggered re-verification when circumstances change.
Regulatory requirements mandate periodic re-screening. Failure to re-screen customers at appropriate intervals can result in regulatory penalties and compliance failures.
## Re-Screening
Use the standalone screening endpoint to re-screen existing customers against PEP, sanctions, adverse media, and watchlist databases.
```bash theme={null}
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/screening/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{
"name": "Jane Smith",
"date_of_birth": "1990-06-15",
"country": "GB",
"entity_type": "person",
"categories": ["pep", "sanctions", "adverse_media", "watchlist"],
"external_reference": "REVIEW-CUST-1234-2026Q1"
}'
```
Re-screening uses the same endpoint and response format as initial screening. See the [Screening Quickstart](/use-cases/screening) for full request and response details.
## Re-Screening Intervals
Schedule re-screening frequency based on the customer's risk tier:
| Risk Tier | Recommended Interval | Rationale |
| - | - | - |
| **High** | Every 6 months | Highest regulatory scrutiny. Frequent changes in sanctions lists and PEP databases. |
| **Medium** | Every 12 months | Standard CDD cycle. Aligns with most regulatory frameworks. |
| **Low** | Every 24 months | Reduced frequency appropriate for low-risk customers with stable profiles. |
These intervals are recommendations. Your compliance program may require different schedules based on jurisdiction, customer type, or regulatory guidance.
### Implementing a Re-Screening Schedule
Implement a review calendar that automatically schedules re-screening based on the risk tier returned in the compliance report. Use the `next_review_date` field from the compliance metadata to track when each customer is due.
```javascript screening-scheduler.js theme={null}
async function scheduleReScreening(customerId, riskTier) {
const intervals = {
High: 6, // months
Medium: 12,
Low: 24
};
const months = intervals[riskTier] || 12;
const nextReviewDate = new Date();
nextReviewDate.setMonth(nextReviewDate.getMonth() + months);
await db.customers.update(customerId, {
next_screening_date: nextReviewDate,
risk_tier: riskTier
});
}
// Run daily: find customers due for re-screening
async function processScheduledScreenings() {
const due = await db.customers.find({
next_screening_date: { $lte: new Date() },
status: "active"
});
for (const customer of due) {
await submitScreening(customer);
}
}
```
## Check Validity and Expiry
Verification checks have defined validity periods. After a check expires, it should be re-run before relying on its results.
| Check Category | Validity Period | Rationale |
| - | - | - |
| **Identity** | 365 days | Identity documents and database records are relatively stable |
| **Screening** | 90 days | PEP, sanctions, and adverse media databases update frequently |
| **Company** | 180 days | Company registry data changes with filings, director appointments |
| **Financial** | 365 days | Financial records change annually with audits |
| **EDD** | 180 days | Enhanced due diligence findings may become stale quickly |
Zenoo tracks check expiry dates internally. The compliance report includes validity information for each check:
```json theme={null}
{
"checks_summary": {
"checks": [
{
"type": "Company Screening",
"status": "Pass",
"completed_at": "2026-01-15T14:30:20Z",
"valid_until": "2026-04-15T14:30:20Z"
},
{
"type": "Company Registration",
"status": "Pass",
"completed_at": "2026-01-15T14:30:15Z",
"valid_until": "2026-07-15T14:30:15Z"
}
]
}
}
```
Use the `valid_until` field to schedule check renewal before expiry.
## Cross-Case Deduplication
When running a new verification on an entity that has been verified before, Zenoo automatically evaluates whether existing checks are still valid.
You are not charged for reused checks. Zenoo automatically deduplicates checks across cases, so you only pay for checks that are actually executed.
**How it works:**
1. You submit a verification for an entity.
2. Zenoo checks if the same entity has valid (non-expired) checks from a previous verification.
3. Valid checks are reused. Only expired or missing checks are run fresh.
4. Reused checks are marked with their original source case for audit traceability.
This means:
* You are not charged for reused checks.
* Results are returned faster (reused checks are instant).
* Audit trail shows the provenance of each check.
Cross-case deduplication is automatic. You do not need to implement any special logic. Just submit verifications as normal.
### What Gets Reused
| Check Type | Reuse Eligible | Condition |
| - | - | - |
| Company Registration | Yes | Within validity period |
| PEP/Sanctions Screening | Yes | Within 90-day validity |
| Identity Verification | Yes | Within 365-day validity |
| Document Verification | Yes | Within 365-day validity, document not expired |
| Financial Checks | Yes | Within 365-day validity |
| Biometric Liveness | No | Always re-run (presence must be current) |
## Triggered Re-Verification
Beyond scheduled re-screening, certain events should trigger an immediate re-verification:
**Action:** New UBO, ownership restructure
**Checks to Run:** Full Company Verification (registry + screening on new individuals)
**Action:** News article, regulatory action
**Checks to Run:** Screening re-run + manual review
**Action:** Company re-domiciles, individual relocates
**Checks to Run:** Full re-verification in new jurisdiction
**Action:** New sanctions list update, regulatory directive
**Checks to Run:** Screening re-run on affected entities
**Action:** Name change, new documents
**Checks to Run:** Identity re-verification
**Action:** Scheduled review date reached
**Checks to Run:** Screening at minimum, full re-verification for high risk
**Action:** Unusual activity flagged by your systems
**Checks to Run:** Screening + EDD
For triggered re-verification, submit a new verification request with a new `external_reference` that reflects the trigger:
```bash theme={null}
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"external_reference": "TRIGGER-ADVERSE-ACME-2026-0215"
}'
```
## Batch Screening
For ongoing monitoring of your entire customer base, submit multiple entities to the screening endpoint. Each request screens one entity, but you can submit many requests in parallel.
```javascript batch-screening.js theme={null}
async function batchScreen(customers) {
const CONCURRENCY = 10; // respect rate limits
const results = [];
for (let i = 0; i < customers.length; i += CONCURRENCY) {
const batch = customers.slice(i, i + CONCURRENCY);
const batchResults = await Promise.all(
batch.map((customer) =>
screenEntity({
name: customer.name,
date_of_birth: customer.dob,
country: customer.country,
entity_type: customer.type,
categories: ["pep", "sanctions", "adverse_media", "watchlist"],
external_reference: `BATCH-${customer.id}-${Date.now()}`
})
)
);
results.push(...batchResults);
// Respect rate limits between batches
if (i + CONCURRENCY < customers.length) {
await sleep(1000);
}
}
return results;
}
```
Monitor your rate limits when running batch operations. Contact Zenoo to request higher limits for large batch runs.
## Monitoring Alerts
When re-screening returns new matches, Zenoo generates alerts in the case management system:
| Alert Type | Trigger | Severity |
| - | - | - |
| PEP Match | Entity newly appears in a PEP database | Medium (Class 3-4) or High (Class 1-2) |
| Sanctions Hit | Entity newly appears on a sanctions list | Critical. Immediate escalation required. |
| Adverse Media | New negative media articles found | Medium. Manual review required. |
| Status Change | Previously clear entity now has matches | Depends on match type |
New matches found during re-screening are compared against the previous screening results. Only genuinely new matches generate alerts, not previously reviewed and dismissed matches.
## Next Review Date
The compliance report includes a recommended next review date based on the customer's risk tier:
```json theme={null}
{
"compliance_metadata": {
"cdd_completed": true,
"cdd_completed_at": "2026-01-15T14:32:00Z",
"next_review_date": "2026-07-15",
"risk_tier": "High",
"edd_required": false
}
}
```
Use the `next_review_date` field to schedule the next periodic review in your system. This date is calculated based on the risk tier using the intervals in the re-screening table above.
### Building a Review Calendar
```javascript review-calendar.js theme={null}
async function processCompletedVerification(result) {
const { case_reference, compliance_metadata } = result;
// Store review date from the compliance report
await db.reviews.create({
case_reference,
customer_id: result.external_reference,
completed_at: compliance_metadata.cdd_completed_at,
next_review: compliance_metadata.next_review_date,
risk_tier: compliance_metadata.risk_tier
});
// Schedule a reminder 30 days before review is due
const reminderDate = new Date(compliance_metadata.next_review_date);
reminderDate.setDate(reminderDate.getDate() - 30);
await scheduler.schedule(reminderDate, "review-reminder", {
case_reference,
customer_id: result.external_reference
});
}
```
## Next Steps
* [Screening Quickstart](/use-cases/screening) -- Screening endpoint details and match interpretation
* [Decision Logic](/use-cases/kyb-verification) -- Verdict mapping and auto-approve criteria
* [Idempotency](/guides/idempotency) -- Using external\_reference for re-verification requests
* [Webhooks](/guides/webhooks) -- Receive alerts from re-screening results
# Pagination
Source: https://docs.zenoo.com/guides/pagination
Navigate large result sets using cursor-based pagination across all Zenoo API list endpoints.
# Pagination
All Zenoo API list endpoints use cursor-based pagination. This provides stable, performant pagination even on large datasets with concurrent modifications.
## Request format
| Parameter | Type | Default | Description |
| - | - | - | - |
| `cursor` | string | null | Pagination cursor from previous response |
| `limit` | number | 25 | Results per page (min 1, max 100) |
```bash theme={null}
# First page
curl -X GET "https://api.zenoo.com/v1/alerts?status=Open&limit=25" \
-H "Authorization: Bearer your-api-key"
# Next page
curl -X GET "https://api.zenoo.com/v1/alerts?status=Open&limit=25&cursor=eyJpZCI6ImFsdF8wMjUiLCJzIjoiMjAyNi0wMi0xNlQxMDowMDowMFoifQ" \
-H "Authorization: Bearer your-api-key"
```
## Response format
Every list response includes a `meta.page` object:
```json theme={null}
{
"data": [
{ "token": "alt_001", "title": "PEP Match: John Doe", "status": "Open" },
{
"token": "alt_002",
"title": "Sanctions Hit: Acme Corp",
"status": "Open"
}
],
"meta": {
"total": 142,
"page": {
"cursor": "eyJpZCI6ImFsdF8wMjUiLCJzIjoiMjAyNi0wMi0xNlQxMDowMDowMFoifQ",
"has_more": true
}
}
}
```
| Field | Type | Description |
| - | - | - |
| `meta.total` | number | Total count of records matching the query |
| `meta.page.cursor` | string | Opaque cursor to pass for the next page. `null` if no more pages. |
| `meta.page.has_more` | boolean | Whether more results exist beyond this page |
## Full iteration example
```python Python theme={null}
import requests
def get_all_alerts(status="Open"):
url = "https://api.zenoo.com/v1/alerts"
headers = {"Authorization": "Bearer your-api-key"}
params = {"status": status, "limit": 100}
all_alerts = []
while True:
response = requests.get(url, headers=headers, params=params)
data = response.json()
all_alerts.extend(data["data"])
if not data["meta"]["page"]["has_more"]:
break
params["cursor"] = data["meta"]["page"]["cursor"]
return all_alerts
alerts = get_all_alerts()
print(f"Total: {len(alerts)} alerts")
```
```javascript Node.js theme={null}
async function getAllAlerts(status = "Open") {
const headers = { Authorization: "Bearer your-api-key" };
const allAlerts = [];
let cursor = null;
do {
const params = new URLSearchParams({ status, limit: "100" });
if (cursor) params.set("cursor", cursor);
const response = await fetch(`https://api.zenoo.com/v1/alerts?${params}`, {
headers
});
const data = await response.json();
allAlerts.push(...data.data);
cursor = data.meta.page.has_more ? data.meta.page.cursor : null;
} while (cursor);
return allAlerts;
}
const alerts = await getAllAlerts();
console.log(`Total: ${alerts.length} alerts`);
```
```bash curl (loop) theme={null}
CURSOR=""
PAGE=1
while true; do
if [ -z "$CURSOR" ]; then
RESPONSE=$(curl -s "https://api.zenoo.com/v1/alerts?status=Open&limit=25" \
-H "Authorization: Bearer your-api-key")
else
RESPONSE=$(curl -s "https://api.zenoo.com/v1/alerts?status=Open&limit=25&cursor=$CURSOR" \
-H "Authorization: Bearer your-api-key")
fi
echo "Page $PAGE: $(echo $RESPONSE | jq '.data | length') results"
HAS_MORE=$(echo $RESPONSE | jq -r '.meta.page.has_more')
if [ "$HAS_MORE" = "false" ]; then
break
fi
CURSOR=$(echo $RESPONSE | jq -r '.meta.page.cursor')
PAGE=$((PAGE + 1))
done
```
## Sorting and filtering
Cursors are stable across sort orders and filters. You can combine pagination with any sort or filter parameter:
```bash theme={null}
# Paginate open alerts sorted by priority (descending)
curl -X GET "https://api.zenoo.com/v1/alerts?status=Open&sort=-priority_score&limit=25&cursor=eyJ..." \
-H "Authorization: Bearer your-api-key"
# Paginate case audit logs filtered by date
curl -X GET "https://api.zenoo.com/v1/cases/cas_xyz789/audit-logs?from=2026-01-01T00:00:00Z&limit=50&cursor=eyJ..." \
-H "Authorization: Bearer your-api-key"
```
Do not modify the sort or filter parameters between paginated requests. Changing filters while iterating may cause duplicate or missing results. Start a new pagination sequence if you need different filters.
## Endpoints supporting pagination
| Endpoint | Default limit | Max limit |
| - | - | - |
| `GET /v1/alerts` | 25 | 100 |
| `GET /v1/cases` | 25 | 100 |
| `GET /v1/cases/{token}/alerts` | 25 | 100 |
| `GET /v1/cases/{token}/checks` | 25 | 100 |
| `GET /v1/cases/{token}/entities` | 25 | 100 |
| `GET /v1/cases/{token}/audit-logs` | 50 | 200 |
| `GET /v1/cases/{token}/comments` | 25 | 100 |
| `GET /v1/entities` | 25 | 100 |
| `GET /v1/audit-logs` | 50 | 200 |
## Why cursor-based pagination
Cursor-based pagination has advantages over offset-based (`?page=2&per_page=25`) pagination:
| Aspect | Cursor-based | Offset-based |
| - | - | - |
| **Performance** | O(1) -- seeks directly to position | O(n) -- scans and skips rows |
| **Consistency** | Stable under concurrent inserts/deletes | Rows shift, causing duplicates/gaps |
| **Deep pages** | Same speed for page 1 and page 1000 | Degrades linearly with offset |
## Next steps
* [Alert Review Quickstart](/use-cases/alert-review). Paginated alert listing in action.
* [Bulk Operations](/guides/bulk-operations). Process multiple records in a single request.
# Result Delivery
Source: https://docs.zenoo.com/guides/result-delivery
Choose between pull, webhook push, and ping+pull methods for receiving verification results.
# Result Delivery
Zenoo supports three methods for delivering verification results. Choose based on your infrastructure, latency requirements, and integration complexity.
## Pull via Token
The simplest approach. After initiating a verification, poll the pull endpoint until results are ready.
```mermaid theme={null}
sequenceDiagram
participant Y as Your System
participant Z as Zenoo
Y->>Z: POST /init
Z-->>Y: 200 OK + tokens (pull, start)
Y->>Z: GET /sharable-payload/{pull}
Z-->>Y: 204 No Content (still processing)
Y->>Z: GET /sharable-payload/{pull}
Z-->>Y: 200 OK + full results
```
**How it works:**
1. Initiate a verification (sync or async). Save the `pull` token from the response.
2. Poll `GET /sharable-payload/{pull_token}` at intervals.
3. When results are ready, the endpoint returns `200` with the full payload.
### Polling Strategy
| Time Window | Poll Interval | Rationale |
| - | - | - |
| 0 to 2 minutes | Every 10 seconds | Most automated checks (screening, registry) complete in this window |
| 2 to 10 minutes | Every 30 seconds | Allows for slower providers or queued checks |
| After 10 minutes | Stop polling | Switch to webhook or contact support |
```javascript poll-results.js theme={null}
async function pollResults(pullToken) {
const maxAttempts = 30;
for (let i = 0; i < maxAttempts; i++) {
const response = await fetch(
`https://instance.prod.onboardapp.io/api/gateway/sharable-payload/${pullToken}`
);
if (response.status === 200) return await response.json();
if (response.status === 404) throw new Error("Token invalid or expired");
// 204: still processing
const delay = i < 12 ? 10_000 : 30_000;
await new Promise((r) => setTimeout(r, delay));
}
throw new Error("Polling timeout. Results not ready after 10 minutes.");
}
```
### Pull Endpoint Response Codes
| Status | Meaning |
| - | - |
| `200` | Results are ready. Response body contains the full verification payload. |
| `204` | Still processing. Retry after a short delay. |
| `404` | Token is invalid or has expired. |
### Pull Token Lifetime
Pull tokens remain valid for **30 days** after results become available. After that, the token expires and returns `404`. Store results in your own system before the token expires.
## Webhook Push
Zenoo pushes the full verification results to your endpoint as soon as they are ready. No polling needed.
```mermaid theme={null}
sequenceDiagram
participant Y as Your System
participant Z as Zenoo
Y->>Z: POST /init
Z-->>Y: 200 OK + tokens
Note over Z: Processing...
Z->>Y: POST /your-webhook (verification.completed + full results)
Y-->>Z: 200 OK
```
**How it works:**
1. Configure a webhook endpoint during onboarding.
2. Initiate a verification.
3. Zenoo POSTs the `verification.completed` event with full results to your endpoint.
4. Your endpoint returns `200` within 30 seconds.
This is the fastest delivery method. Results arrive the moment processing finishes, with no polling delay.
**Requirements:**
* HTTPS endpoint accessible from the internet.
* Signature verification on every incoming webhook.
* Response within 30 seconds.
* Idempotent event handling (Zenoo may deliver the same event more than once).
See the [Webhooks](/guides/webhooks) guide for full setup instructions.
## Ping + Pull
A lightweight webhook notification tells you results are ready. You then fetch the full payload at your convenience.
```mermaid theme={null}
sequenceDiagram
participant Y as Your System
participant Z as Zenoo
Y->>Z: POST /init
Z-->>Y: 200 OK + tokens
Note over Z: Processing...
Z->>Y: POST /your-webhook (status + pull_token only)
Y-->>Z: 200 OK
Y->>Z: GET /sharable-payload/{pull_token}
Z-->>Y: 200 OK + full results
```
**How it works:**
1. Configure a webhook endpoint.
2. Initiate a verification.
3. Zenoo sends a small notification (status + pull token, no result data).
4. Your system fetches full results from the pull endpoint when ready.
This keeps webhook payloads small and decouples notification from data retrieval. Your system controls when and how it fetches results.
**Notification payload:**
```json theme={null}
{
"event_type": "verification.completed",
"status": "complete",
"callback_reference": "APP-2026-0123",
"pull_token": "eyJhbGciOiJIUzI1NiJ9.pull-token...",
"completed_at": "2026-01-15T14:35:00Z"
}
```
**Then fetch results:**
```bash theme={null}
curl "https://instance.prod.onboardapp.io/api/gateway/sharable-payload/{pull_token}"
```
## Comparison
| Aspect | Pull via Token | Webhook Push | Ping + Pull |
| - | - | - | - |
| **Mechanism** | Poll endpoint at intervals | Full results pushed to your endpoint | Lightweight notification, you fetch results |
| **Latency** | Depends on poll interval (10-30s) | Near-instant | Near-instant notification, you control fetch timing |
| **Complexity** | Lowest | Medium (webhook endpoint + sig verification) | Medium (webhook + pull call) |
| **Reliability** | High (you control timing) | Depends on webhook delivery | High (notification + guaranteed pull) |
| **Payload size** | N/A (you fetch on demand) | Full results in webhook | Small notification, full results on pull |
| **Best for** | POC, low-volume, simple integrations | Real-time processing, event-driven architectures | Production systems, high-reliability requirements |
## Recommendation
For production, use Ping + Pull. You get real-time notification that results are available, with the flexibility to fetch full results on your own schedule. Small webhook payloads are more reliable to deliver, and you always have the pull endpoint as a fallback.
**For POC and development:** Use Pull via Token. It requires no webhook infrastructure. Just initiate a verification and poll for results.
**For event-driven architectures:** Use Webhook Push. If your system is already built around event processing (message queues, serverless functions), receiving full results via webhook fits naturally.
### Combining Methods
The most resilient pattern uses webhooks with polling as a fallback:
1. Configure webhooks for instant notification (push or ping+pull).
2. Implement a background job that polls for any verifications that have been pending longer than expected.
3. If a webhook is missed (endpoint down, network issue), the polling job catches it.
```javascript fallback-poller.js theme={null}
// Background job: catch any missed webhooks
async function checkPendingVerifications() {
const pending = await db.verifications.find({
status: "pending",
initiated_at: { $lt: new Date(Date.now() - 10 * 60 * 1000) } // >10 min ago
});
for (const v of pending) {
const response = await fetch(
`https://instance.prod.onboardapp.io/api/gateway/sharable-payload/${v.pull_token}`
);
if (response.status === 200) {
const results = await response.json();
await processResults(v.id, results);
}
}
}
```
## Next Steps
* [Webhooks](/guides/webhooks) -- Set up webhook endpoints and signature verification
* [Sync vs Async Flows](/guides/sync-vs-async) -- Choose between sync and async execution
* [Error Handling](/guides/error-handling) -- Handle failures and timeouts during result retrieval
# Risk Scoring
Source: https://docs.zenoo.com/guides/risk-scoring
Understand the 4-dimension risk model, scoring algorithm, tier assignment, and risk assessment lifecycle.
# Risk Scoring
Zenoo implements a FATF-aligned, 4-dimension risk assessment model. Every case receives a risk score across four dimensions, producing an overall risk tier that drives due diligence requirements and review frequency.
## The 4 dimensions
Risk is assessed independently across four dimensions. Each dimension scores 0-100 and maps to a tier:
| Dimension | What it measures | Example factors |
| - | - | - |
| **Customer** | Inherent risk of the customer entity | PEP status, sanctions exposure, adverse media, industry |
| **Geographic** | Country and jurisdiction risk | FATF grey/black list, EU High-Risk, Basel AML Index |
| **Product/Service** | Risk associated with the products or services | Financial services, crypto, high-value transactions |
| **Channel** | Risk from the delivery channel | Online-only, face-to-face, third-party introduction |
```mermaid theme={null}
flowchart LR
A[Case Alerts + Entity Data] --> B[Customer Score]
A --> C[Geographic Score]
A --> D[Product Score]
A --> E[Channel Score]
B --> F[Overall Risk]
C --> F
D --> F
E --> F
F --> G{Highest Tier Wins}
G -->|High| H[High Risk]
G -->|Medium| I[Medium Risk]
G -->|Low| J[Low Risk]
```
## Scoring algorithm
Each dimension score is calculated from the alerts and entity attributes associated with the case. The system maps alert categories and entity properties to dimensions using configurable rules.
### Score to tier mapping
| Score Range | Tier |
| - | - |
| 70-100 | `High` |
| 40-69 | `Medium` |
| 0-39 | `Low` |
These thresholds are configurable per risk model. The values above are defaults. You can adjust `high_threshold` and `medium_threshold` when creating or modifying a risk model.
### Overall tier calculation
The overall risk tier is determined by the **highest-tier-wins** rule: if any single dimension is rated High, the overall tier is High. This conservative approach ensures that concentrated risk in any dimension triggers appropriate due diligence.
## Risk assessment lifecycle
```mermaid theme={null}
stateDiagram-v2
[*] --> Draft : Assessment created
Draft --> Active : Scores calculated
Active --> Approved : Reviewer approves
Active --> Overridden : Analyst overrides tier
Overridden --> Approved : Override approved
Approved --> Superseded : New assessment created
Superseded --> [*]
Approved --> [*]
```
| Status | Description |
| - | - |
| `Draft` | Assessment created but dimension scores not yet calculated |
| `Active` | Scores calculated, awaiting review |
| `Superseded` | Replaced by a newer assessment |
| `Overridden` | Analyst has manually overridden the calculated tier |
| `Approved` | Reviewed and approved -- becomes the effective assessment |
## Analyst overrides
Analysts can override the calculated tier when additional context justifies a different risk rating. Overrides require:
1. A new tier selection (High, Medium, or Low)
2. A documented justification
3. Approval by a second reviewer (four-eyes principle)
```bash theme={null}
curl -X POST https://api.zenoo.com/v1/risk-assessments/rsk_abc123/override \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"override_tier": "Low",
"reason": "PEP match is for a former local councillor with no current exposure. Customer has 10-year clean banking history."
}'
```
The override is logged in the audit trail and the effective tier becomes the override value until a new assessment supersedes it.
## Risk factors
Each dimension can include specific risk factors that explain the score:
```json theme={null}
{
"dimension": "Customer",
"score": 65,
"tier": "Medium",
"factors": [
"UBO identified as former PEP (councillor, 2010-2014)",
"Financial Services industry classification",
"No adverse media findings"
]
}
```
Factors are generated from:
* **Alert-based factors**: Each resolved alert category contributes a factor with its severity
* **Entity attribute factors**: Jurisdiction, industry, corporate structure complexity
* **Country risk factors**: FATF status, EU High-Risk listing, Basel AML Index score
## EDD triggers
When the overall risk tier is `High`, Enhanced Due Diligence (EDD) requirements are automatically added:
| Trigger | EDD Requirement |
| - | - |
| Overall tier = High | Source of Wealth check for UBOs and Directors |
| Sanctions exposure | Enhanced screening with ongoing monitoring |
| Senior PEP | Mandatory senior reviewer approval |
| Complex structure | Full corporate structure verification |
## Configurable risk models
Zenoo supports multiple configurable risk models with:
* **Per-model thresholds**: Customize High/Medium score thresholds
* **Custom rules**: Define scoring rules by alert category, entity attribute, or country data
* **Champion/Challenger**: A/B test new models in shadow mode before promoting
* **What-If Analysis**: Backtest parameter changes against historical assessments
* **AI Tuning**: Generate data-driven optimization recommendations
## Next steps
* [Alert Management](/guides/alert-management). How alerts contribute to risk scoring.
* [Audit Trail](/guides/audit-trail). Track all risk assessment events.
* [Cases Data Model](/data-model/cases). Case-level risk fields.
* [Status Enums](/reference/status-enums). All risk tier and assessment status values.
# How Do I Brand My Verification Experience?
Source: https://docs.zenoo.com/guides/studio/brand-your-experience
Customize the customer-facing verification journey with your logo, brand colors, welcome messages, and email templates.
# How Do I Brand My Verification Experience?
When customers complete a verification journey, they interact with a Zenoo-hosted experience. You can fully customize this experience to match your brand — from colors and logos to welcome messages and email templates.
## What you'll learn
* Where to find branding settings
* How to upload your logo and set brand colors
* How to customize welcome and completion messages
* How to configure email templates
* How to preview on mobile and desktop
## What can I customize?
| Element | What it controls |
| - | - |
| **Logo** | Displayed in the header of every screen the customer sees |
| **Brand colors** | Primary color (buttons, links, progress bar), background color, text color |
| **Welcome message** | The first screen customers see when they open the verification link |
| **Completion message** | The final screen after verification is submitted |
| **Email templates** | The invitation email, reminder emails, and confirmation emails sent to customers |
| **Favicon** | The browser tab icon |
| **Font** | Primary font family for the verification UI |
## How do I set up branding?
From the Studio dashboard, navigate to **Settings** and then **Branding**. The branding editor opens with a live preview on the right.
Click the logo placeholder and upload your company logo. Supported formats: PNG, SVG, or JPEG. Recommended size: 200 x 60 pixels.
The logo appears in the header of every verification screen. Use a horizontal/landscape version for the best fit.
Upload a logo with a transparent background (PNG or SVG) so it works well against both light and dark backgrounds.
Configure your color palette:
* **Primary color** — used for buttons, links, the progress bar, and interactive elements
* **Background color** — the page background
* **Text color** — primary text color
* **Accent color** — used for highlights and secondary elements
Enter hex color codes or use the color picker. The live preview updates instantly as you make changes.
The welcome screen is the first thing customers see. Customize:
* **Headline** — e.g., "Verify your identity"
* **Body text** — explain what the customer needs (ID document, selfie) and how long it takes
* **Button text** — e.g., "Start verification" or "Let's go"
Keep the message short and clear. Customers are more likely to complete verification when they know what to expect.
The completion screen appears after the customer finishes. Customize:
* **Headline** — e.g., "Thank you"
* **Body text** — explain next steps (e.g., "We'll review your submission and get back to you within 24 hours")
* **Redirect URL** (optional) — send customers back to your app after a delay
The completion screen should never reveal the verification decision. Your compliance team makes the final call in Case Management. The customer receives the outcome through your application's own notification system.
Zenoo sends emails at key points in the journey. Customize each template:
* **Invitation email** — contains the verification link. Set subject line, greeting, and call-to-action text
* **Reminder email** — sent if the customer has not completed verification within a configurable time period
* **Confirmation email** — sent after submission to confirm receipt
All templates support merge fields for personalization (customer name, company name, expiry time).
Use the preview toggle in the top-right corner to switch between mobile and desktop views. Walk through each screen to verify:
* Logo displays correctly at both sizes
* Colors are readable and accessible
* Text fits without overflow
* Buttons are tappable on mobile
Test on actual devices before going live. The preview is accurate but does not replicate every browser and OS combination.
Click **Save** to store your branding. Changes take effect on the next journey that is created — existing active journey URLs continue to use the previous branding.
## What's next?
Make sure branding and everything else is production-ready.
See how the branded experience fits into a full KYC flow.
Create the verification flow behind the branded experience.
Set up the verification providers your flow will use.
# How Do I Connect Providers?
Source: https://docs.zenoo.com/guides/studio/connect-providers
Add verification providers to Zenoo Studio — enter credentials, configure settings, test connections, and enable them in your flows.
# How Do I Connect Providers?
Zenoo Studio acts as an orchestration layer that routes checks to external verification providers. Before you can use a provider in your flows, you need to connect it with API credentials and configure its settings.
## What you'll learn
* Which provider types are supported
* How to add a new provider
* How to configure timeout, retry, and batch settings
* How to test the connection
* How to enable a provider in your flows
## What providers does Zenoo support?
Zenoo supports providers across several verification categories:
| Category | Providers | What they do |
| - | - | - |
| **Screening** | WorldCheck, ComplyAdvantage, Sumsub | PEP, sanctions, adverse media, and watchlist screening |
| **Company Registry** | Kyckr | Company registration lookup, director/UBO extraction |
| **Identity** | Document verification providers | ID document capture, OCR, authenticity checks |
| **Biometric** | Liveness detection providers | Selfie matching, liveness verification, deepfake detection |
| **Address** | Address verification providers | Proof of address validation |
New providers can be added to the platform without code changes. Each provider is implemented as an adapter that follows a standard interface, making the system extensible.
## How do I add a provider?
From the Studio dashboard, open **Settings** in the left navigation, then select **Providers**. You will see a list of configured providers and an **Add Provider** button.
Click **Add Provider** and choose from the available provider catalog. Each entry shows:
* Provider name and logo
* Categories supported (screening, identity, company, etc.)
* Whether it operates in individual or batch mode
Fill in the credentials for your provider account:
* **API Key / API Secret** — provided by the vendor when you sign up
* **Environment** — select Staging or Production (use staging for testing)
* **Base URL** — pre-filled for known providers, editable for custom endpoints
Store production API credentials securely. Never share them in emails or chat. Zenoo encrypts credentials at rest using Named Credentials.
Set operational parameters for the provider:
* **Timeout (seconds)** — how long to wait for a response before treating the call as failed (default: 30 seconds)
* **Max retries** — how many times to retry on transient failures (default: 3)
* **Retry delay** — seconds between retry attempts
These settings prevent a single slow provider from blocking your entire verification flow.
Some providers support **batch mode**, where a single API call returns results for multiple check types. For example, WorldCheck returns PEP, Sanctions, and Adverse Media results in one call.
If your provider supports batch mode:
* Toggle **Category Batch Mode** on
* Select which check types should be grouped in a single call
Click **Test Connection**. Studio sends a test request to the provider's API using your credentials and displays:
* **Connection status** — success or failure
* **Response time** — how long the test took
* **Error details** — if the test failed, what went wrong (invalid credentials, network error, timeout)
Always test with staging credentials first. Switch to production credentials only when you are ready to go live.
Once the connection test passes, toggle the provider to **Active**. It will now appear as an option when configuring check nodes in the flow builder.
## How do I switch providers?
If you need to change a provider (e.g., migrating from one screening vendor to another):
1. Add and test the new provider using the steps above
2. Open your flow in the builder
3. Click each check node that uses the old provider
4. Change the **Provider** dropdown to the new provider
5. Save and test the flow
6. Deactivate the old provider once you confirm everything works
Check results from the old provider remain attached to their original cases. Switching providers only affects new flow executions.
## What's next?
Use your connected provider in a verification flow.
Configure screening nodes with your screening provider.
Verify all providers are configured with production credentials before launch.
Customize the customer-facing verification journey.
# Go-Live Checklist
Source: https://docs.zenoo.com/guides/studio/go-live-checklist
Everything you need to verify before launching your Zenoo verification flows in production.
# Go-Live Checklist
Before launching your verification flows in production, work through this checklist to make sure everything is configured, tested, and ready. Each section covers a critical area — skip nothing.
## What you'll learn
* Every configuration that needs to be verified before go-live
* Common issues that cause launch-day problems
* How to validate each area
## Provider Configuration
* [ ] **All providers have production credentials** — staging credentials will not work in production. Switch each provider from staging to production API keys
* [ ] **Connection tests pass for every provider** — go to Settings, Providers, and click Test Connection on each active provider
* [ ] **Timeout and retry settings are tuned** — production providers may have different response times than staging. Set timeouts to at least 30 seconds and max retries to 3
* [ ] **Batch mode is configured correctly** — if your screening provider supports category batch mode, verify it is enabled to avoid redundant API calls
Do not reuse staging API keys in production. Most providers enforce environment separation and will reject staging keys in production environments.
## Verification Flows
* [ ] **All flows are tested end-to-end** — run each flow with realistic sample data in the staging environment and verify every node produces expected results
* [ ] **Decision logic is correct** — verify that auto-approve, manual review, and rejection paths all route correctly
* [ ] **Error paths are handled** — test what happens when a provider is unavailable or returns an error. Verify that failed checks generate appropriate alerts in Case Management
* [ ] **Screening thresholds are set for production** — testing thresholds (70%) may be too loose for production. Review and tighten as needed (80-90%)
## Webhooks
* [ ] **Webhook endpoint URL is configured** — provide Zenoo with your production webhook URL
* [ ] **Webhook secret is stored securely** — stored in an environment variable or secrets manager, not hardcoded
* [ ] **Signature verification is implemented** — your endpoint validates the `X-Zenoo-Signature` header on every incoming request
* [ ] **Endpoint responds within 30 seconds** — test under load to verify your endpoint does not time out
* [ ] **Idempotency is implemented** — your handler uses `journey_id` + `event_type` to deduplicate events
Test your webhook endpoint by triggering a few verification flows in staging and verifying that events arrive and are processed correctly.
## Branding
* [ ] **Logo uploaded** — correct format (PNG/SVG), correct dimensions, transparent background
* [ ] **Brand colors set** — primary, background, text, and accent colors match your brand guidelines
* [ ] **Welcome message customized** — clear, professional, and explains what the customer needs
* [ ] **Completion message customized** — does not reveal the verification outcome
* [ ] **Email templates customized** — invitation, reminder, and confirmation emails use your brand voice
* [ ] **Mobile preview tested** — verify the experience looks correct on mobile devices
## Case Management
* [ ] **Compliance team has access** — all analysts, reviewers, and officers have the correct permission sets assigned
* [ ] **SLA thresholds configured** — verify SLA due dates, warning periods, and auto-escalation rules match your policy
* [ ] **Reviewer assignments set up** — senior reviewers and escalation targets are configured
* [ ] **Auto-triage settings reviewed** — if enabled, verify which alert types are auto-triaged and auto-disposed
* [ ] **Audit trail is working** — create a test case, resolve it, and verify the full audit trail is captured
## Team & Permissions
* [ ] **Team members are invited** — all users who need access have active accounts
* [ ] **Roles are assigned correctly** — analysts, senior reviewers, and officers have the right permission levels
* [ ] **Escalation paths are clear** — each analyst has a manager configured for auto-escalation
* [ ] **Notification preferences are set** — email notifications and in-app notifications are enabled for relevant events
## Monitoring & Alerts
* [ ] **SLA monitoring is scheduled** — the nightly SLA batch job is active and will run daily
* [ ] **Error alerting is configured** — you will be notified if a provider goes down or a critical flow fails
* [ ] **Analytics dashboards are accessible** — team leads and compliance officers can view the analytics hub
## Final Steps
Execute 3-5 verification flows in staging that cover your most common scenarios (clean customer, PEP match, sanctions hit, document issue). Walk through the entire process from initiation to case resolution.
Confirm that data flows correctly: Studio flow executes, results arrive in Case Management, alerts are generated, analyst resolves them, case is closed, and the decision is delivered back.
Update provider credentials to production, confirm webhook URLs, and deploy your flows to the production environment.
Watch the analytics dashboard closely for the first day. Verify flow completion rates, provider response times, and alert volumes are within expected ranges.
## What's next?
Your compliance team is ready to start reviewing alerts.
Monitor SLA performance from day one.
Verify that every action is being logged for regulatory compliance.
Full guide library for your compliance team.
# Studio Guides
Source: https://docs.zenoo.com/guides/studio/index
Learn how to build compliance workflows, connect providers, and launch branded verification journeys in Zenoo Studio.
Zenoo Studio is where you design, test, and manage your compliance verification workflows. No code required — everything is configured through a visual interface.
## What you'll learn
* How to build and test verification flows using the visual flow builder
* How to verify companies (KYB) and individuals (KYC)
* How to configure sanctions and PEP screening
* How to connect external verification providers
* How to brand your customer-facing verification experience
* How to prepare for production launch
## Who are these guides for?
Studio guides are for **compliance managers**, **operations leads**, and **product teams** who design and maintain verification processes. If you are a compliance analyst reviewing cases, head to the [Case Management Guides](/guides/case-management) instead.
## Recommended reading order
If you are new to Studio, read the guides in this order:
1. **Build Your First Flow** — understand the flow builder interface and core concepts
2. **Connect Providers** — add your verification providers before building real flows
3. **Verify a Person** or **Verify a Company** — follow a complete walkthrough for your use case
4. **Screen for Sanctions** — configure screening databases and thresholds
5. **Brand Your Experience** — customize what your customers see
6. **Go-Live Checklist** — verify everything before launching
If you already have flows running, skip to the specific guide you need.
## Guides
Create a verification flow from scratch — add check nodes, configure providers, test with sample data, and view results.
Walk through a KYB flow: submit company data, run registry checks, extract directors and UBOs, and screen each person.
Walk through a KYC flow: initiate a journey, capture documents, run biometric checks, screen against watchlists, and review results.
Configure screening nodes for PEP, sanctions, adverse media, and watchlist databases. Set match thresholds and interpret results.
Add verification providers (WorldCheck, Kyckr, document services), enter credentials, test connections, and enable them in your flows.
Customize the customer-facing verification journey with your logo, colors, messages, and email templates.
Make sure everything is production-ready before you launch — credentials, webhooks, branding, error handling, and team permissions.
## How does Studio connect to Case Management?
When a verification flow completes and issues are found (screening hits, document failures, identity mismatches), Studio automatically creates a **case** in Case Management. The case includes all entities, check results, and generated alerts. Your compliance team picks up from there.
If all checks pass with no issues, the verification is auto-approved and no case is created — unless your flow is configured to always require manual review.
## What's next?
Once your flows are live and generating results, your compliance team will review them in Case Management.
Learn how your compliance team reviews, investigates, and resolves cases generated by Studio.
Understand how Studio and Case Management work together.
# How Do I Screen for Sanctions?
Source: https://docs.zenoo.com/guides/studio/screen-for-sanctions
Configure screening nodes in Zenoo Studio to check against PEP, sanctions, adverse media, and watchlist databases.
# How Do I Screen for Sanctions?
Screening is the process of checking a person or company name against global databases of sanctioned individuals, politically exposed persons, adverse media, and law enforcement watchlists. This guide covers how to set up screening in Studio and how to interpret the results.
## What you'll learn
* Which screening databases are available
* How to configure a screening node in the flow builder
* How to set match thresholds
* How to test screening with known names
* How to read screening hit cards
## What databases can I screen against?
Zenoo connects to multiple screening providers, each covering different databases:
| Database | What it covers | Why it matters |
| - | - | - |
| **PEP Lists** | Current and former government officials, their family members, and close associates | PEPs carry higher corruption and bribery risk. Most regulations require enhanced due diligence. |
| **Sanctions Lists** | OFAC (US), EU Consolidated, UN Security Council, UK HMT, and 30+ country-specific lists | Dealing with sanctioned entities is a criminal offense in most jurisdictions. |
| **Adverse Media** | Negative news from global media sources related to financial crime, fraud, terrorism | Early warning of reputational and financial crime risk. |
| **Watchlists** | Law enforcement, customs, Interpol, and regulatory enforcement lists | Identifies persons of interest to authorities. |
The specific databases available depend on your screening provider. WorldCheck covers all four categories in a single API call. Other providers may require separate configurations.
## How do I add screening to my flow?
Navigate to your project and open the flow you want to add screening to. If you do not have a flow yet, see [Build Your First Flow](/guides/studio/your-first-flow).
From the node palette on the left, find **Screening** under the "Checks" category. Drag it onto the canvas and connect it to the appropriate upstream node.
For KYB flows, you typically place screening after the registry check (so extracted directors and UBOs are screened automatically). For KYC flows, it usually runs in parallel with identity verification.
Click the node to open its configuration panel. Under **Provider**, select your connected screening provider (e.g., WorldCheck, ComplyAdvantage, Sumsub).
If no providers appear, you need to connect one first. See [Connect Providers](/guides/studio/connect-providers).
Select which databases to screen against:
* **PEP** — Politically Exposed Persons
* **Sanctions** — Global sanctions lists
* **Adverse Media** — Negative news coverage
* **Watchlists** — Law enforcement and customs lists
For onboarding flows, enable all four databases. For ongoing monitoring, you may want to focus on sanctions and PEP only to reduce noise.
The match threshold controls how similar a name must be to a database entry to generate a hit. Set this as a percentage:
| Threshold | Effect | Best for |
| - | - | - |
| **90-100%** | Only exact or near-exact matches | Production with high volume, where you want fewer false positives |
| **80-89%** | Catches common name variations | Balanced approach for most teams |
| **70-79%** | Catches looser matches (transliterations, aliases) | Regulated environments requiring broader coverage |
| **Below 70%** | Very broad matching | Testing only; generates excessive false positives in production |
Save your flow, then click **Test Flow**. Enter a name you know will generate a hit (your screening provider usually supplies test names in their documentation).
Review the results to confirm:
* Hits are being generated at your chosen threshold
* The correct databases are being checked
* Match details include enough information for your team to review
## How do I read a screening hit?
When screening finds a match, it generates a hit card with these details:
* **Matched name** — the name in the database that triggered the match
* **Match score** — confidence percentage (0-100%) of how closely the names match
* **Category** — PEP, Sanctions, Adverse Media, or Watchlist
* **Source** — which specific database or list the match came from
* **Details** — additional context like country, date of birth, position held, or media article summaries
A screening hit does not mean the person is definitely on a sanctions list. It means a name in the database is similar enough to warrant manual review. Your compliance team makes the final determination in Case Management.
## What happens after screening?
* **No hits** — the flow continues to the next node or auto-approves
* **Hits found** — alerts are created in Case Management for each hit type (one alert per category). Your compliance team reviews them in the [alert workspace](/guides/case-management/review-alerts)
* **Screening error** — if the provider is unavailable, the check fails and an API Failure alert is created
## What's next?
Learn how your compliance team reviews screening alerts in Case Management.
Set up your screening provider credentials.
See how screening fits into a full KYB verification flow.
Verify your screening configuration before launching.
# How Do I Verify a Company?
Source: https://docs.zenoo.com/guides/studio/verify-a-company
Walk through a KYB verification flow in Zenoo Studio — from company submission to director screening and compliance report.
# How Do I Verify a Company?
Company verification (KYB) is more complex than individual verification because it involves multiple entities. A single company may have directors, UBOs, and shareholders — each requiring their own checks. This guide walks through the full KYB flow in Studio.
## What you'll learn
* How company data enters the system
* What happens during a registry check
* How directors and UBOs are extracted and screened
* What the compliance report looks like
* When cases are created in Case Management
## The KYB flow at a glance
```mermaid theme={null}
flowchart TD
A[Company data submitted] --> B[Registry Check]
B --> C{Company found?}
C -->|Yes| D[Extract Directors & UBOs]
C -->|No| E[Manual Review Required]
D --> F[Screen each person]
F --> G{Hits found?}
G -->|No| H[Auto-approved]
G -->|Yes| I[Case created in CLM]
E --> I
```
## Walk through each stage
The flow begins when company data arrives — either from your application via the API or from a customer filling in a branded journey form.
Key data points submitted:
* **Company name** and **registration number**
* **Country of incorporation**
* **Registered address**
* **Website** (optional)
Studio validates the input fields and starts executing the flow.
The first node in a KYB flow is typically a **Company Registry** check. This calls the configured registry provider (e.g., Kyckr) to look up the company.
The registry check verifies:
* **Registration status** — is the company active, dissolved, or struck off?
* **Name match** — does the submitted name match the registry name? Studio calculates a similarity score and flags mismatches below 70%
* **Registered address** — does the address match?
* **Company type** — what kind of entity is this?
A dissolved or inactive company generates a high-priority alert immediately. The flow continues to extract directors and UBOs, but the case will require manual review.
If the registry check finds the company, Studio automatically extracts:
* **Directors and officers** — names, roles, appointment dates
* **Shareholders** — names, ownership percentages
* **UBOs (Ultimate Beneficial Owners)** — individuals with 25%+ ownership or significant control
Each person becomes a **case entity** linked to the company case. Their role (Director, UBO, Shareholder) determines which checks are required.
| Role | Identity | Screening | Address | Adverse Media | Source of Wealth |
| - | - | - | - | - | - |
| UBO | Required | Required | Required | Required | If High Risk |
| Director | Required | Required | N/A | Required | If High Risk |
| Shareholder | Required | Required | N/A | N/A | N/A |
Studio runs PEP/sanctions/adverse media screening on every extracted person. This typically uses a single API call per person to a screening provider like WorldCheck.
Each screening result can return:
* **No Hit** — no matches found
* **Hit** — one or more matches found, with a match score and details
Hits generate **alerts** in Case Management with the match details, source data, and a confidence score.
After all checks complete, Studio compiles a compliance report showing:
* **Company verification status** — registry result, name match score, company status
* **Entity list** — all directors, UBOs, and shareholders with their check results
* **Screening summary** — how many people were screened, how many had hits
* **Overall decision** — auto-approved (all clear) or review required (hits found)
If any issues were found, a **case** is automatically created in Case Management with all entities, checks, and alerts pre-populated.
## What does the admin see in Studio?
After a KYB flow completes, the Studio dashboard shows:
* **Flow execution log** — each node with its status (passed/flagged/failed), execution time, and provider response
* **Entity tree** — the company and all extracted persons in a hierarchy view
* **Screening results** — match summaries per person with hit counts
* **Journey status** — approved, in review, or failed
## What's next?
Walk through a KYC flow for individual identity verification.
Configure screening databases, thresholds, and match interpretation.
Learn how to manage the UBO tree and entity checks in Case Management.
Understand how checks work once they land in Case Management.
# How Do I Verify a Person?
Source: https://docs.zenoo.com/guides/studio/verify-a-person
Walk through a KYC verification flow — from initiating a journey to document capture, biometric matching, screening, and results.
# How Do I Verify a Person?
Individual verification (KYC) validates a person's identity through document capture, biometric matching, and sanctions screening. This guide walks through both the customer-facing experience and the admin view in Studio.
## What you'll learn
* How a verification journey is initiated
* What the customer sees during verification
* How document capture and biometric matching work
* What screening runs in the background
* How results are delivered
## The KYC flow at a glance
```mermaid theme={null}
flowchart LR
A[Journey initiated] --> B[Customer opens URL]
B --> C[Document capture]
C --> D[Selfie + liveness]
D --> E[Biometric match]
E --> F[Screening]
F --> G{All clear?}
G -->|Yes| H[Approved]
G -->|No| I[Review Required]
```
## The customer experience
When your application initiates a verification journey, Zenoo generates a unique, time-limited URL. The customer receives this link via email, SMS, or embedded in your app.
The link is valid for 24 hours. If it expires, a `journey.expired` event fires and you can generate a new one.
The customer opens the link and sees a branded welcome screen with your logo, colors, and a brief explanation of what they need to do. This screen is fully customizable (see [Brand Your Experience](/guides/studio/brand-your-experience)).
The welcome screen lists:
* Which document types are accepted (passport, national ID, driving license)
* That a selfie will be required
* Estimated completion time
The customer photographs their identity document using their device camera. The system guides them with:
* **Frame overlay** — shows where to position the document
* **Quality checks** — detects blur, glare, and cropping issues in real time
* **Auto-capture** — takes the photo automatically when the document is properly positioned
For two-sided documents (e.g., national ID cards), the customer captures both sides.
After document capture, the customer takes a selfie. The liveness check ensures the person is physically present:
* **Active liveness** — the customer follows prompts (turn head, blink) to prove they are not using a photo or video
* **Passive liveness** — advanced algorithms detect spoofing without user prompts
The liveness method depends on your flow configuration.
While checks run in the background, the customer sees a processing screen. This typically takes 10-30 seconds. The customer can close the browser — results will be delivered asynchronously.
Once processing finishes, the customer sees a completion message. The message is generic (e.g., "Thank you, we'll be in touch") — it does not reveal the verification outcome, which is only visible to your team.
## What happens behind the scenes?
While the customer completes their journey, Studio runs checks in parallel:
* **OCR extraction** — extracts name, date of birth, document number, expiry date, nationality from the document image
* **Authenticity checks** — analyzes security features (holograms, microprint, UV patterns) to detect tampering
* **Expiry validation** — flags expired documents
* **MRZ verification** — validates the machine-readable zone against visual data
* **Face comparison** — matches the selfie against the document photo
* **Match score** — generates a confidence score (0-100%)
* **Liveness result** — confirms the person is physically present
* **Deepfake detection** — analyzes for AI-generated images or video injection
* **PEP check** — screens against Politically Exposed Persons databases
* **Sanctions check** — screens against global sanctions lists (OFAC, EU, UN, UK)
* **Adverse media** — screens news sources for negative coverage
* **Watchlists** — screens against law enforcement and customs watchlists
## What does the admin see?
In the Studio dashboard, each completed journey shows:
* **Identity result** — verified/not verified, with the document type and extracted data
* **Biometric result** — face match score, liveness result
* **Document result** — authenticity score, any tampering flags, expiry status
* **Screening result** — PEP/sanctions/adverse media hit status with match details
If any check produces a hit or failure, a case is automatically created in Case Management. Your compliance team will see it in their alert queue.
## What generates alerts?
| Check Result | Alert Created? | Alert Type |
| - | - | - |
| Document expired | Yes | Document |
| Name mismatch (OCR vs. submitted) | Yes | Identity |
| Face match below threshold | Yes | Identity |
| Liveness check failed | Yes | Identity |
| PEP match found | Yes | Screening |
| Sanctions hit found | Yes | Screening |
| Adverse media found | Yes | Screening |
| All checks pass | No | -- |
## What's next?
Deep dive into configuring screening databases and match thresholds.
Customize the verification journey your customers see.
Learn how your compliance team reviews alerts from verification results.
Start from scratch with a step-by-step flow builder walkthrough.
# Build Your First Flow
Source: https://docs.zenoo.com/guides/studio/your-first-flow
Create a verification flow from scratch in Zenoo Studio — add check nodes, configure them, test with sample data, and view results.
# Build Your First Flow
This guide walks you through creating a basic verification flow in Zenoo Studio. By the end, you will have a working flow that verifies a person's identity, screens them against sanctions lists, and returns a result.
## What you'll learn
* How to create a new project and flow in Studio
* How to add and configure check nodes in the visual builder
* How to connect nodes to build a verification sequence
* How to test your flow with sample data
* How to read the results
## Before you start
Make sure you have:
* A Zenoo Studio account with admin or editor permissions
* At least one verification provider connected (see [Connect Providers](/guides/studio/connect-providers))
* Access to the staging environment for testing
## Create your flow
From the Studio dashboard, click **New Project**. Give it a name (e.g., "Customer Onboarding") and select the project type — **Person Verification** for KYC or **Company Verification** for KYB.
Each project acts as a container for related flows. You might have one project per product line or customer segment.
Inside your new project, click **Create Flow**. The visual flow builder opens with a blank canvas and a **Start** node already placed.
The flow builder has three areas:
* **Canvas** (center) — where you arrange and connect nodes
* **Node palette** (left sidebar) — available check types you can drag onto the canvas
* **Configuration panel** (right sidebar) — settings for the selected node
From the node palette, drag **Identity Verification** onto the canvas. Connect it to the **Start** node by dragging from the Start output port to the Identity input port.
Click the node to open its configuration:
* **Provider** — select your identity verification provider
* **Document types accepted** — choose which ID documents to accept (passport, national ID, driving license)
* **Liveness check** — toggle on to require a selfie with liveness detection
Drag a **Screening** node onto the canvas and connect it after the Identity Verification node.
Configure the screening node:
* **Provider** — select your screening provider (e.g., WorldCheck)
* **Databases** — select PEP, Sanctions, and Adverse Media
* **Match threshold** — set the minimum match score to flag (e.g., 80%)
Start with a lower threshold (70-80%) during testing to see more matches. You can tighten it before going live.
Drag a **Decision** node after the screening node. This is where the flow determines the outcome.
The decision node evaluates all upstream check results:
* **All checks pass, no screening hits** — auto-approve
* **Screening hits found** — route to Case Management for manual review
* **Check failures** — route to Case Management or reject automatically
Add **Approved** and **Review Required** end nodes. Connect the Decision node's outputs to the appropriate end nodes.
Your flow should now look like: Start, Identity Verification, Screening, Decision, with branches to Approved and Review Required.
Click **Save** in the top toolbar. Then click **Test Flow** to open the test runner.
Enter sample data:
* **First name** and **Last name**
* **Date of birth**
* **Country**
Click **Run Test**. Studio executes the flow using your staging provider credentials and displays the results in real time.
The test runner shows each node's result:
* A green checkmark for nodes that passed
* A yellow warning for nodes that flagged issues (screening hits)
* A red cross for nodes that failed
Click any node to see its detailed output — identity match score, screening hit details, document authenticity results.
## What happens when issues are found?
When a flow routes someone to "Review Required," Zenoo automatically creates a **case** in Case Management. The case includes:
* The person's details as a case entity
* All check results as required checks
* Any screening hits as alerts
Your compliance team picks up the case from there. See [How Do Cases Work?](/guides/case-management/understand-cases) for details.
## What's next?
Build a KYB flow with registry checks, director extraction, and multi-entity screening.
Deep dive into screening configuration — databases, thresholds, and interpreting match results.
Add and configure external verification providers.
Make sure everything is production-ready before launch.
# Sync vs Async
Source: https://docs.zenoo.com/guides/sync-vs-async
Choose between synchronous and asynchronous execution models based on your integration requirements.
# Sync vs Async Flows
Zenoo supports two execution models. Choose based on whether user interaction is required and how your system handles latency.
Quick rule: server-to-server checks use sync. User-facing journeys use async.
## Synchronous (Model 1)
The API blocks until all checks complete or the timeout is reached. Results are returned in the response body.
```mermaid theme={null}
sequenceDiagram
participant Y as Your Server
participant Z as Zenoo
participant P as Providers
Y->>Z: POST /api (X-SYNC-TIMEOUT)
Z->>P: Execute checks
P-->>Z: Results
Z-->>Y: 200 OK + full results
```
### How to use
Add the `X-SYNC-TIMEOUT` header to any `/api` endpoint. The value is in milliseconds.
```bash theme={null}
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-H "X-SYNC-TIMEOUT: 60000" \
-d '{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB"
}'
```
The API holds the connection open for up to `X-SYNC-TIMEOUT` milliseconds, then returns whatever results are available.
### When to use
* Server-to-server checks: AML screening, company registry verification
* Automated pipelines: batch processing, background jobs, cron tasks
* Simple flows: single-provider checks that complete quickly
* Development and testing: fastest way to see results
### Timeout behavior
If the response contains a `tokens` field, some checks are still processing. Use the `pull` token to retrieve the remaining results.
| Scenario | Response |
| - | - |
| All checks complete within timeout | `200` with full results |
| Timeout reached, some checks pending | Partial results with `tokens` for polling the rest |
| Provider error | Error response with details and retry guidance |
### Recommended timeouts
| Check type | Timeout | Notes |
| - | - | - |
| AML screening only | 15,000 ms | Single provider call, usually completes in 3-10s |
| Company registry lookup | 30,000 ms | Depends on jurisdiction, some registries are slower |
| Full Company (registry + screening) | 60,000 ms | Multiple providers in sequence |
| Server-side Person (database + phone) | 30,000 ms | Multiple lightweight checks in parallel |
Setting timeouts below 10 seconds forces unnecessary polling. Setting them above 90 seconds ties up HTTP connections without benefit. Start with the recommended values and adjust based on your p95 latency.
## Asynchronous (Model 2)
The API returns immediately with tokens. Processing happens in the background.
```mermaid theme={null}
sequenceDiagram
participant Y as Your Server
participant Z as Zenoo
participant P as Providers
Y->>Z: POST /init
Z-->>Y: 200 OK + tokens (pull, start)
Z->>P: Execute checks (background)
P-->>Z: Results
Y->>Z: GET /sharable-payload/{pull}
Z-->>Y: 204 (still processing)
Y->>Z: GET /sharable-payload/{pull}
Z-->>Y: 200 OK + full results
```
### How to use
Call the `/init` endpoint without the `X-SYNC-TIMEOUT` header.
```bash theme={null}
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/kyc/init" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-d '{
"first_name": "Jane",
"last_name": "Smith",
"date_of_birth": "1990-06-15",
"country": "GB",
"external_reference": "YOUR-REF-2026-0123"
}'
```
**Response:**
```json theme={null}
{
"tokens": {
"pull": "eyJhbGciOiJIUzI1NiJ9.pull-token...",
"start": "eyJhbGciOiJIUzI1NiJ9.start-token..."
}
}
```
### When to use
* User-facing journeys: Person Verification document capture, biometric liveness
* Long-running flows: multi-provider checks, manual review steps
* User interaction required: the user must upload a document or complete liveness
* High-volume processing: avoid holding HTTP connections open
### Token pair
The response contains two tokens:
| Token | Purpose | Lifetime |
| - | - | - |
| `pull` | Retrieve results via `GET /api/gateway/sharable-payload/{pull}` | Until results expire |
| `start` | Construct the verification URL for user-facing flows | 24 hours |
Store both tokens in your database immediately. The `pull` token is the only way to retrieve results. If you lose it, you must reinitiate the verification.
### Verification URL
For Person Verification flows, construct the verification URL from the `start` token:
```
https://instance.prod.onboardapp.io/{project_hash}/?t={start_token}
```
Redirect the user to this URL to begin document capture and biometric verification. The URL expires after 24 hours.
### Result retrieval
**Polling.** Call the pull endpoint and check the HTTP status code.
```bash theme={null}
curl -X GET \
"https://instance.prod.onboardapp.io/api/gateway/sharable-payload/{pull_token}"
```
| Status | Meaning |
| - | - |
| `200` with JSON body | Results are ready |
| `204` No Content | Still processing, retry after a delay |
| `404` | Invalid or expired token |
**Webhook.** Configure an endpoint to receive `verification.completed` events. See [Webhooks Guide](/guides/webhooks).
**Both (recommended).** Use webhooks for real-time notification, with polling as a fallback for missed webhooks.
## Comparison table
| Aspect | Sync (Model 1) | Async (Model 2) |
| - | - | - |
| **Endpoint suffix** | `/api` | `/init` |
| **Response type** | Full results (or partial + tokens) | Tokens only |
| **Blocking** | Yes, up to timeout | No |
| **User interaction** | Not supported | Supported via start token |
| **Result retrieval** | In response body | Pull endpoint or webhook |
| **Best for** | Screening, Company, server-side Person | Person with document capture, biometrics |
| **Timeout control** | `X-SYNC-TIMEOUT` header (ms) | N/A |
| **Typical latency** | 10-60 seconds | Instant (tokens), results in 30s-5min |
## Processing timeline
Typical durations for each processing stage:
| Stage | Duration |
| - | - |
| API gateway routing | \< 1 second |
| Company registry lookup | 5-15 seconds |
| PEP/sanctions screening (WorldCheck) | 3-10 seconds |
| Document verification (after user submits) | 10-30 seconds |
| Biometric liveness (after user submits) | 5-15 seconds |
| Risk model evaluation | 1-5 seconds |
| **Full Company Verification (automated)** | **15-45 seconds** |
| **Full Person Verification (user-dependent)** | **1-30 minutes** |
Person Verification elapsed time is dominated by user behavior: finding documents, taking photos, retrying failed captures. Once the user submits, server-side processing completes in 30 to 60 seconds.
## Mixing models
Most production integrations use both models. A typical pattern:
1. **Company Verification** (sync). Submit company data, get results in the response.
2. **Person Verification** (async). Initiate journeys for directors and UBOs, redirect them to verification URLs.
3. **Screening** (sync). Re-screen existing customers for ongoing monitoring.
```javascript integration.js theme={null}
// 1. Company Verification - sync
const companyResult = await fetch(
`${BASE_URL}/api/gateway/execute/${PROJECT_HASH}/api`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": API_KEY,
"X-SYNC-TIMEOUT": "60000"
},
body: JSON.stringify({
company_name: "Acme Holdings Ltd",
registration_number: "12345678",
country: "GB"
})
}
);
const companyData = await companyResult.json();
// 2. Person Verification - async (for each director/UBO)
const personInit = await fetch(
`${BASE_URL}/api/gateway/execute/${PROJECT_HASH}/kyc/init`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": API_KEY
},
body: JSON.stringify({
first_name: "Jane",
last_name: "Smith",
country: "GB",
external_reference: "YOUR-REF-2026-0123"
})
}
);
const { tokens } = await personInit.json();
const verificationUrl = `${BASE_URL}/${PROJECT_HASH}/?t=${tokens.start}`;
// Send verificationUrl to the user via email or in-app
// 3. Screening - sync (ongoing monitoring)
const screenResult = await fetch(
`${BASE_URL}/api/gateway/execute/${PROJECT_HASH}/screening/api`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": API_KEY,
"X-SYNC-TIMEOUT": "15000"
},
body: JSON.stringify({
name: "John Smith",
date_of_birth: "1975-03-20",
country: "GB",
entity_type: "person",
categories: ["pep", "sanctions", "adverse_media", "watchlist"]
})
}
);
```
## Polling example (Node.js)
A complete polling implementation with exponential backoff:
```javascript poll-results.js theme={null}
async function pollForResults(pullToken, options = {}) {
const {
baseUrl = "https://instance.prod.onboardapp.io",
maxAttempts = 30,
initialDelay = 10_000, // 10 seconds
maxDelay = 30_000, // 30 seconds
backoffAfter = 12 // switch to maxDelay after 12 attempts (2 min)
} = options;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const response = await fetch(
`${baseUrl}/api/gateway/sharable-payload/${pullToken}`
);
if (response.status === 200) {
return await response.json();
}
if (response.status === 404) {
throw new Error("Invalid or expired pull token");
}
if (response.status !== 204) {
throw new Error(`Unexpected status: ${response.status}`);
}
// 204 - still processing
const delay = attempt < backoffAfter ? initialDelay : maxDelay;
await new Promise((resolve) => setTimeout(resolve, delay));
}
throw new Error(`Polling timed out after ${maxAttempts} attempts`);
}
// Usage
try {
const results = await pollForResults("eyJhbGciOiJIUzI1NiJ9.pull-token...");
console.log("Verification complete:", results.processing_status);
} catch (err) {
console.error("Polling failed:", err.message);
}
```
## Next steps
* [Webhooks Guide](/guides/webhooks). Set up async result delivery.
* [Error Handling](/guides/error-handling). Handle timeouts, retries, and provider failures.
* [Testing and Sandbox](/testing/staging-environment). Test both models in the staging environment.
# Webhooks
Source: https://docs.zenoo.com/guides/webhooks
Configure webhook endpoints to receive real-time notifications when verification events occur.
# Webhooks
Webhooks deliver real-time notifications to your system when verification events occur. Instead of polling for results, configure a webhook endpoint and Zenoo will POST event payloads as they happen.
## Events
| Event | Trigger | Typical Action |
| - | - | - |
| `verification.completed` | Person/Company Verification journey finishes with results | Process results, update customer record |
| `screening.completed` | PEP/sanctions screening finishes | Review matches, update risk profile |
| `journey.abandoned` | User closes the verification UI without finishing | Send reminder, create new journey |
| `journey.expired` | Verification URL expires (24h default) | Generate new journey, notify user |
| `check.failed` | A check fails after all retries are exhausted | Alert compliance team, investigate manually |
## Setup
During onboarding, give Zenoo the HTTPS URL where you want to receive events:
```
https://api.yourapp.com/webhooks/zenoo
```
Zenoo generates an HMAC secret for signature verification. Store it securely (environment variable, secrets manager).
Every webhook includes an `X-Zenoo-Signature` header. Verify it before processing any payload:
```javascript server.js theme={null}
const crypto = require("crypto");
function verifySignature(payload, signature, secret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(payload).digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
```
Your endpoint must respond with a `200-299` status code within 30 seconds. If it does not, Zenoo treats the delivery as failed and retries.
Process events asynchronously. Accept the webhook, store the payload, and handle business logic in a background job.
## Payload Structure
All webhook events share this envelope:
```json theme={null}
{
"event_type": "verification.completed",
"timestamp": "2026-01-15T14:35:00Z",
"journey_id": "jrn-abc123",
"callback_reference": "APP-2026-0123",
"status": "completed",
"data": {
// Event-specific payload
}
}
```
| Field | Description |
| - | - |
| `event_type` | The event name (e.g., `verification.completed`) |
| `timestamp` | ISO 8601 timestamp of when the event occurred |
| `journey_id` | Unique identifier for the verification journey |
| `callback_reference` | The `external_reference` you provided when initiating the verification |
| `status` | Current journey status |
| `data` | Event-specific payload (varies by event type) |
The `callback_reference` echoes back the value you set when initiating the verification. Use it to correlate webhook events with records in your system.
## Event Payloads
### verification.completed
Fires when a Person or Company Verification journey completes. The `data` field contains the full verification results, identical to the pull endpoint response.
```json theme={null}
{
"event_type": "verification.completed",
"timestamp": "2026-01-15T14:35:00Z",
"journey_id": "jrn-abc123",
"callback_reference": "APP-2026-0123",
"status": "completed",
"data": {
"case_reference": "AML-2026-0042",
"overall_verdict": "Pass",
"risk_tier": "Low",
"identity": { "verified": true, "decision": "Pass", "score": 95 },
"document": {
"verified": true,
"document_type": "Passport",
"tamper_result": "Pass"
},
"biometric": {
"liveness_verified": true,
"face_match": true,
"face_match_score": 96.2
},
"screening": {
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "No Hit"
}
}
}
```
### screening.completed
Fires when standalone screening completes.
```json theme={null}
{
"event_type": "screening.completed",
"timestamp": "2026-01-15T14:30:20Z",
"journey_id": "jrn-def456",
"callback_reference": "SCREEN-2026-0099",
"status": "completed",
"data": {
"entity_name": "John Smith",
"entity_type": "person",
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "Hit",
"adverse_media_count": 2,
"screening_provider": "WorldCheck"
}
}
```
### journey.abandoned
Fires when a user closes the verification UI or navigates away without completing it.
```json theme={null}
{
"event_type": "journey.abandoned",
"timestamp": "2026-01-15T15:00:00Z",
"journey_id": "jrn-abc123",
"callback_reference": "APP-2026-0123",
"status": "abandoned",
"data": {}
}
```
### journey.expired
Fires when the 24-hour verification URL expires before the user completes the flow.
```json theme={null}
{
"event_type": "journey.expired",
"timestamp": "2026-01-16T14:35:00Z",
"journey_id": "jrn-abc123",
"callback_reference": "APP-2026-0123",
"status": "expired",
"data": {}
}
```
### check.failed
Fires when a check fails after all automatic retry attempts are exhausted. This is a terminal state.
```json theme={null}
{
"event_type": "check.failed",
"timestamp": "2026-01-15T16:00:00Z",
"journey_id": "jrn-abc123",
"callback_reference": "APP-2026-0123",
"status": "failed",
"data": {
"check_type": "KYB_REGISTRATION",
"error_category": "Permanent",
"retry_count": 3,
"failure_reason": "Company not found in registry"
}
}
```
Do not retry the same check after receiving this event. Investigate the cause (invalid data, provider outage, missing information) and either correct the issue or escalate.
## Retry Logic
If your endpoint does not return `200-299` within 30 seconds, Zenoo retries with increasing delays:
| Attempt | Delay After Failure |
| - | - |
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 1 hour |
| 5th through 168th | Every 1 hour for 7 days |
After 7 days of consecutive failures, the webhook is marked as undeliverable. Contact Zenoo support to replay failed events.
## Idempotency
Your webhook handler must be idempotent. Zenoo may deliver the same event more than once in edge cases (network timeouts, retry storms).
Use `journey_id` combined with `event_type` as a deduplication key to prevent processing the same event twice.
```javascript server.js theme={null}
app.post("/webhooks/zenoo", async (req, res) => {
const { journey_id, event_type } = req.body;
const dedupeKey = `${journey_id}:${event_type}`;
// Check for duplicate delivery
const existing = await db.webhookEvents.findOne({ dedupeKey });
if (existing) {
// Already processed. Return 200 to stop retries.
return res.status(200).json({ received: true, duplicate: true });
}
// Store the event before processing
await db.webhookEvents.create({
dedupeKey,
journey_id,
event_type,
payload: req.body,
received_at: new Date()
});
// Process asynchronously
await queue.enqueue("process-webhook", req.body);
res.status(200).json({ received: true });
});
```
Always return `200` for duplicate events. Returning an error causes unnecessary retries.
## Error Handling
How Zenoo responds to your endpoint's status codes:
| Your Response | Zenoo Behavior |
| - | - |
| `200-299` | Delivery confirmed, no retry |
| `3xx` | Follows redirect (up to 3 hops) |
| `400-499` | Retries with backoff (may be transient) |
| `500-599` | Retries with backoff |
| Timeout (>30s) | Retries with backoff |
| Connection refused | Retries with backoff |
Return `200 OK` immediately, then process the event in a background job. This is the single most important implementation detail for reliable webhook handling.
## Testing
### Staging Webhooks
Configure your staging webhook endpoint during onboarding. All verification flows in the staging environment trigger the same webhook events as production, using mock provider data.
### Local Development
Use a tunneling tool to expose your local server:
```bash theme={null}
# Start your webhook server locally
node server.js # listening on port 3000
# Expose with ngrok
ngrok http 3000
# Configure your webhook URL as:
# https://abc123.ngrok.io/webhooks/zenoo
```
### Webhook Replay
Contact Zenoo support to replay specific webhook events. This is useful for:
* Debugging payload parsing issues
* Testing idempotency handling
* Recovering from endpoint downtime
## Security Checklist
* [ ] Verify `X-Zenoo-Signature` on every incoming webhook
* [ ] Use HTTPS for your webhook endpoint (no plain HTTP)
* [ ] Respond with `200` within 30 seconds
* [ ] Handle duplicate deliveries (idempotent processing)
* [ ] Log failed signature verifications as security events
* [ ] Process event payloads asynchronously (queue + worker)
* [ ] Allowlist Zenoo IP ranges in production firewalls
Never skip signature verification. Without it, your endpoint accepts forged requests from anyone who knows your URL.
## Signature Verification
Zenoo signs all outbound webhook payloads using HMAC-SHA256. Every webhook request includes a signature in the `X-Zenoo-Signature` header. Verify this signature before processing the payload.
### Signature format
```
X-Zenoo-Signature: sha256=a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890
```
The value is a `sha256=` prefix followed by the hex-encoded HMAC-SHA256 digest of the raw request body, computed with your webhook secret.
### Verification steps
Extract the `X-Zenoo-Signature` header value from the incoming request.
Remove the `sha256=` prefix to isolate the hex digest.
Compute HMAC-SHA256 of the raw request body using your webhook secret.
Compare the computed digest with the received digest using a constant-time comparison function. If the digests do not match, reject the request with `401 Unauthorized`. Do not process the payload.
### Code examples
```javascript Node.js theme={null}
const crypto = require("crypto");
function verifyWebhookSignature(rawBody, signature, secret) {
const expectedSig = crypto
.createHmac("sha256", secret)
.update(rawBody, "utf8")
.digest("hex");
const receivedSig = signature.replace("sha256=", "");
return crypto.timingSafeEqual(
Buffer.from(expectedSig, "hex"),
Buffer.from(receivedSig, "hex")
);
}
// Express middleware
app.post("/webhooks/zenoo", (req, res) => {
const signature = req.headers["x-zenoo-signature"];
const isValid = verifyWebhookSignature(
req.rawBody,
signature,
process.env.ZENOO_WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).json({ error: "Invalid signature" });
}
// Process the webhook
res.status(200).json({ received: true });
});
```
```python Python theme={null}
import hmac
import hashlib
def verify_webhook_signature(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
received = signature.replace('sha256=', '')
return hmac.compare_digest(expected, received)
# Flask route
@app.route('/webhooks/zenoo', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Zenoo-Signature', '')
is_valid = verify_webhook_signature(
request.get_data(),
signature,
os.environ['ZENOO_WEBHOOK_SECRET']
)
if not is_valid:
return jsonify({'error': 'Invalid signature'}), 401
# Process the webhook
return jsonify({'received': True}), 200
```
```java Java theme={null}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
public class WebhookVerifier {
public static boolean verify(String rawBody, String signature, String secret)
throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
byte[] hash = mac.doFinal(rawBody.getBytes("UTF-8"));
String expected = bytesToHex(hash);
String received = signature.replace("sha256=", "");
return MessageDigest.isEqual(
expected.getBytes("UTF-8"),
received.getBytes("UTF-8")
);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
```
Express does not provide `req.rawBody` by default. Use the `body-parser` middleware with `verify` option or `express.raw()` to capture the raw body before JSON parsing:
```javascript theme={null}
app.use(
express.json({
verify: (req, res, buf) => {
req.rawBody = buf;
}
})
);
```
### Constant-time comparison
Always use a constant-time comparison function when verifying signatures. Standard string equality operators (`==`, `===`, `.equals()`) are vulnerable to timing attacks. An attacker can measure response times to incrementally guess the correct signature byte by byte.
| Language | Function |
| - | - |
| Node.js | `crypto.timingSafeEqual()` |
| Python | `hmac.compare_digest()` |
| Java | `MessageDigest.isEqual()` |
| Go | `hmac.Equal()` |
| Ruby | `Rack::Utils.secure_compare()` |
### JWT-encoded webhooks
Some webhook configurations deliver payloads as JWT tokens instead of raw JSON. Zenoo auto-detects the format:
* If the request body starts with `eyJ` and contains exactly 3 dot-separated segments, it is treated as a JWT.
* The JWT payload is decoded and verified against your webhook secret.
* Your verification logic does not need to change. Zenoo handles the decoding transparently.
You do not need to configure anything to enable or disable JWT encoding. The detection is automatic.
### Handling verification failures
When signature verification fails:
1. Reject the request with `401 Unauthorized`.
2. Log the failure with the request timestamp, source IP, and headers.
3. Do not process the payload.
4. Monitor for repeated failures, which may indicate an attack or a misconfigured secret.
If every webhook fails verification, check that your webhook secret matches the one Zenoo provided during onboarding. Secrets are environment-specific. A staging secret will not validate production webhooks.
## Next Steps
Handle API failures and retries
Compare push, pull, and ping+pull models
Prevent duplicate verifications
Test webhook delivery in staging
# Zenoo Documentation
Source: https://docs.zenoo.com/index
Everything you need to automate KYC, KYB, and AML compliance.
Build and manage compliance workflows with Studio, then review results in Case Management.
Learn how to build verification flows in Studio and review cases in Case Management.
Integrate Zenoo via API — authentication, endpoints, webhooks, and data models.
Full endpoint reference for Flows, Entities, Cases, Alerts, Checks, and more.
Error codes, status enums, glossary, test data, and go-live checklist.
## Popular guides
KYB verification with registry lookups, UBO discovery, and director screening.
KYC verification with identity checks, document capture, and biometrics.
PEP, sanctions, adverse media, and watchlist screening with ongoing monitoring.
## Quick start
Send your first API request to create a verification case:
```bash theme={null}
curl -X POST https://api.zenoo.com/v1/cases \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "onboarding",
"entity_category": "company",
"external_reference": "customer-123"
}'
```
Step-by-step walkthrough of authentication, creating a case, and reading results.
API keys, token management, and security best practices.
# Country Codes
Source: https://docs.zenoo.com/reference/country-codes
ISO 3166-1 alpha-2 country codes used across all Zenoo API country, nationality, and jurisdiction fields.
# Country Codes
Zenoo uses ISO 3166-1 alpha-2 country codes for all country fields: `country`, `nationality`, `jurisdiction`, and address-related fields.
## Format
Always pass a two-letter uppercase code.
```json theme={null}
{
"country": "GB",
"nationality": "IE"
}
```
## Common codes
| Country | Code | Notes |
| - | - | - |
| United Kingdom | `GB` | Not `UK` |
| United States | `US` | Not `USA` |
| Ireland | `IE` | |
| Germany | `DE` | |
| France | `FR` | |
| Netherlands | `NL` | |
| Switzerland | `CH` | |
| Australia | `AU` | |
| Canada | `CA` | |
| Singapore | `SG` | |
| Hong Kong | `HK` | |
| United Arab Emirates | `AE` | |
| Cayman Islands | `KY` | |
| Jersey | `JE` | |
| Guernsey | `GG` | |
| Isle of Man | `IM` | |
| British Virgin Islands | `VG` | Not `BVI` |
| Luxembourg | `LU` | |
| Bermuda | `BM` | |
| Gibraltar | `GI` | |
| Malta | `MT` | |
| Cyprus | `CY` | |
| Liechtenstein | `LI` | |
| Monaco | `MC` | |
| South Africa | `ZA` | |
| Nigeria | `NG` | |
| Japan | `JP` | |
| China | `CN` | |
| India | `IN` | |
| Brazil | `BR` | |
| Russia | `RU` | |
## Common mistakes
These are the most frequent country code errors. The API will return a `400 VALIDATION_ERROR` for any invalid code.
| Incorrect | Correct | Reason |
| - | - | - |
| `UK` | `GB` | ISO standard uses `GB` for the United Kingdom |
| `USA` | `US` | Must be exactly 2 characters |
| `BVI` | `VG` | British Virgin Islands uses `VG` in ISO 3166-1 |
| `UAE` | `AE` | United Arab Emirates uses `AE` |
| `EN` | `GB` | `EN` is a language code, not a country code |
| `gb` | `GB` | Must be uppercase |
Submitting an invalid country code returns a `400 VALIDATION_ERROR`:
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Invalid country code: UK. Use ISO 3166-1 alpha-2 (GB).",
"request_id": "req-a1b2c3d4"
}
```
## Supported jurisdictions
Zenoo supports 200+ jurisdictions for screening. Company registry verification coverage varies by jurisdiction.
For a full list of supported jurisdictions and registry coverage, contact your Zenoo account manager or email [support@zenoo.com](mailto:support@zenoo.com).
## Screening vs registry coverage
**Screening** (PEP, sanctions, adverse media) works globally. WorldCheck covers entities across all jurisdictions.
**Company registry** verification requires registry coverage for the specific country. Major markets (GB, US, IE, DE, FR, NL, AU, SG, HK) have full coverage. Some smaller jurisdictions may have limited or no registry access.
If you submit a Company Verification request for a jurisdiction without registry coverage, the company verification check will fail, but screening checks will still run.
## Next steps
* [Error Codes](/reference/error-codes) -- Error responses for invalid country codes
* [Rate Limits](/reference/rate-limits) -- Per-endpoint rate limits
* [Glossary](/reference/glossary) -- Term definitions
# Error Codes
Source: https://docs.zenoo.com/reference/error-codes
Machine-readable error codes, human-readable messages, and retry guidance for every Zenoo API error response.
# Error Codes
Every error response from the Zenoo API includes a machine-readable error code, a human-readable message, and a unique request ID.
## Response format
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Missing required field: company_name",
"request_id": "req-a1b2c3d4"
}
```
| Field | Description |
| - | - |
| `error` | Machine-readable error code. Use this for programmatic error handling. |
| `message` | Human-readable description. Log this, but do not display it to end users. |
| `request_id` | Unique identifier for the request. Include this in support tickets. |
Always log the `request_id` from every error response. It is essential for correlating issues with Zenoo support.
## Error code reference
| Error Code | HTTP Status | Description | Retryable | Recommended Action |
| - | - | - | - | - |
| `VALIDATION_ERROR` | 400 | Missing or invalid required fields | No | Fix the request payload and resubmit |
| `INVALID_JSON` | 400 | Request body is not valid JSON | No | Check for syntax errors in the JSON body |
| `UNAUTHORIZED` | 401 | Missing or invalid API key | No | Verify the `X-API-KEY` header value |
| `FORBIDDEN` | 403 | API key does not have access to this project | No | Contact Zenoo support for access |
| `NOT_FOUND` | 404 | Invalid project hash, endpoint, or resource | No | Verify the URL, project hash, and resource ID |
| `TOKEN_EXPIRED` | 404 | Pull token has expired | No | Initiate a new verification to get fresh tokens |
| `PROCESSING` | 204 | Results still processing (pull endpoint only) | Yes | Retry per polling strategy in [Sync vs Async](/guides/sync-vs-async) |
| `RATE_LIMITED` | 429 | Too many requests | Yes | Wait for the duration in the `Retry-After` header |
| `INTERNAL_ERROR` | 500 | Unexpected server error | Yes | Retry with exponential backoff |
| `PROVIDER_ERROR` | 502 | Upstream provider failure (registry, screening) | Yes | Retry with exponential backoff |
| `SERVICE_UNAVAILABLE` | 503 | Zenoo service temporarily unavailable | Yes | Retry after 30 seconds |
## Examples
Returned when the request is missing required fields or contains invalid values.
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Missing required field: company_name",
"request_id": "req-7f3a2b1c"
}
```
```json theme={null}
{
"error": "VALIDATION_ERROR",
"message": "Invalid country code: UK. Use ISO 3166-1 alpha-2 (GB).",
"request_id": "req-8e4d3c2a"
}
```
Returned when the request body cannot be parsed as JSON.
```json theme={null}
{
"error": "INVALID_JSON",
"message": "Unexpected token at position 42",
"request_id": "req-9a5e4d3b"
}
```
Returned when the `X-API-KEY` header is missing or contains an invalid key.
```json theme={null}
{
"error": "UNAUTHORIZED",
"message": "Invalid API key",
"request_id": "req-1b6f5e4c"
}
```
Returned when the API key is valid but does not have access to the requested project.
```json theme={null}
{
"error": "FORBIDDEN",
"message": "API key does not have access to project abc123",
"request_id": "req-2c7a6f5d"
}
```
Returned when the project hash, endpoint, or resource does not exist.
```json theme={null}
{
"error": "NOT_FOUND",
"message": "Project not found: invalid-hash",
"request_id": "req-3d8b7a6e"
}
```
Returned when a pull token has expired. Pull tokens are valid for **30 days** after results become available. Start tokens (verification URLs) expire after **24 hours**.
```json theme={null}
{
"error": "TOKEN_EXPIRED",
"message": "Pull token has expired. Initiate a new verification.",
"request_id": "req-4e9c8b7f"
}
```
Returned when request volume exceeds your project's rate limit. The `Retry-After` header tells you how many seconds to wait.
```json theme={null}
{
"error": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 60 seconds.",
"request_id": "req-5f0d9c8a"
}
```
**Response headers:**
```
Retry-After: 60
```
Returned for unexpected server errors. These are transient and should be retried.
```json theme={null}
{
"error": "INTERNAL_ERROR",
"message": "An unexpected error occurred. Please retry.",
"request_id": "req-6a1e0d9b"
}
```
Returned when an upstream verification provider (registry, screening) fails. Zenoo automatically retries provider failures for async flows. For sync flows, retry the request.
```json theme={null}
{
"error": "PROVIDER_ERROR",
"message": "Upstream provider temporarily unavailable",
"request_id": "req-7b2f1e0c"
}
```
Returned when the Zenoo service is temporarily unavailable (maintenance, capacity limits). Retry after 30 seconds.
```json theme={null}
{
"error": "SERVICE_UNAVAILABLE",
"message": "Service temporarily unavailable. Please retry.",
"request_id": "req-8c3a2f1d"
}
```
## Retry logic
Only retry errors marked as retryable in the table above. For 4xx errors, fix the request before resubmitting.
Use the following exponential backoff schedule for retryable errors:
```
Attempt 1: Immediate
Attempt 2: Wait 5 seconds
Attempt 3: Wait 25 seconds
Attempt 4: Wait 60 seconds
Maximum: 4 attempts
```
For `RATE_LIMITED` (429), always use the `Retry-After` header value instead of the default backoff schedule.
For detailed retry implementation, see [Error Handling](/guides/error-handling).
## Next steps
* [Rate Limits](/reference/rate-limits) -- Per-endpoint rate limits and best practices
* [Error Handling](/guides/error-handling) -- Retry strategies and circuit breaker behavior
* [Glossary](/reference/glossary) -- Term definitions
# Glossary
Source: https://docs.zenoo.com/reference/glossary
Definitions of key terms used throughout the Zenoo API documentation.
# Glossary
Terms used throughout the Zenoo API documentation, organized alphabetically.
## A
**AML** (Anti-Money Laundering). Regulations and procedures to prevent the use of financial systems for money laundering. Zenoo automates AML compliance checks including Company Verification, Person Verification, and screening.
## C
**CDD** (Customer Due Diligence). Standard identity verification and risk assessment required when onboarding a customer. Includes identity verification, screening, and company registry checks. Zenoo marks CDD as complete when all required checks pass.
**Circuit Breaker**. A resilience pattern that prevents repeated calls to a failing provider. When a provider fails beyond a threshold, the circuit "opens" and requests fail fast instead of waiting for timeouts. After a cooldown period, a single test request is sent. If it succeeds, normal traffic resumes. Zenoo implements circuit breakers internally for all upstream providers.
**Company Verification**. The process of verifying a company's identity, legal status, and ownership structure. Zenoo's Company Verification flow includes company registry verification, director and UBO extraction, and screening of all entities. Also known as KYB (Know Your Business) in regulatory terminology.
## E
**EDD** (Enhanced Due Diligence). Additional verification required for high-risk customers. Triggered by screening hits (PEP, sanctions), high-risk jurisdictions, or complex ownership structures. EDD may require source of funds documentation, source of wealth evidence, or senior compliance review.
**Exponential Backoff**. A retry strategy where the delay between attempts increases exponentially. For example: 5 seconds, 25 seconds, 125 seconds. This prevents overwhelming a recovering service with retry storms.
## F
**5AMLD** (Fifth Anti-Money Laundering Directive). EU directive (effective January 2020) that expanded AML requirements to include cryptocurrency exchanges, art dealers, and tax advisors. Lowered the UBO threshold from 25% to narrower criteria in some jurisdictions.
**6AMLD** (Sixth Anti-Money Laundering Directive). EU directive (effective June 2021) that harmonized the definition of money laundering across member states, extended criminal liability, and increased penalties.
**FATF** (Financial Action Task Force). The international body that sets AML/CFT standards. FATF recommendations form the regulatory baseline for customer due diligence, PEP screening, and sanctions compliance worldwide. FATF also publishes lists of high-risk and non-cooperative jurisdictions.
## H
**HMAC-SHA256** (Hash-based Message Authentication Code using SHA-256). The algorithm Zenoo uses for webhook signature verification. Your webhook endpoint receives a signature in the `X-Zenoo-Signature` header, computed as `sha256=HMAC-SHA256(webhook_secret, request_body)`.
**HMT** (His Majesty's Treasury). The UK government department that maintains the UK sanctions list. HMT sanctions are separate from OFAC (US) and EU sanctions lists. Zenoo screens against HMT via WorldCheck.
## I
**Idempotency**. The property that submitting the same request multiple times produces the same result. Zenoo supports idempotency via the `external_reference` field. If you submit a verification with the same `external_reference` twice, the second request returns the existing case instead of creating a duplicate.
## K
**KYB** (Know Your Business). See **Company Verification**.
**KYC** (Know Your Customer). See **Person Verification**.
**Company Registry Provider**. A verification provider that connects to official government registries to verify company names, registration numbers, addresses, directors, and shareholders. Zenoo integrates with multiple registry providers -- see the [Zenoo Marketplace](https://zenoo.com/marketplace) for the full list.
## O
**OCR** (Optical Character Recognition). The technology used to extract text data from identity documents (passports, national IDs, driving licences) during Person Verification.
**OFAC** (Office of Foreign Assets Control). The US Treasury department that administers and enforces economic sanctions. Maintains the SDN (Specially Designated Nationals) list. Zenoo screens against OFAC via WorldCheck.
## P
**PEP** (Politically Exposed Person). An individual who holds or has held a prominent public function. Examples: heads of state, senior politicians, senior executives of state-owned enterprises, senior military officials. PEPs and their family members and close associates are subject to enhanced due diligence.
**Person Verification**. The process of verifying an individual's identity. Zenoo's Person Verification flow includes document verification, biometric liveness detection, database lookups, and screening. Also known as KYC (Know Your Customer) in regulatory terminology.
**Pull Token**. A short-lived token (valid for 24 hours by default) used to retrieve async verification results from the Zenoo API. Returned in the response to `/init` requests. Use it with the `/sharable-payload/{pull_token}` endpoint.
## S
**SDN** (Specially Designated Nationals and Blocked Persons List). The OFAC-maintained list of individuals and entities whose assets are blocked and with whom US persons are prohibited from transacting. Zenoo screens against the SDN list via WorldCheck.
**SIC** (Sensitive Industry Categories). A Zenoo classification flag indicating an entity is associated with a sensitive industry sector. `hasSIC=true` on a screening result means the entity's industry warrants additional scrutiny. This is distinct from sanctions (`hasSanction`).
**Start Token**. A token returned by the `/init` endpoint, used to construct a client-facing verification URL for interactive Person Verification flows (document capture, biometric liveness).
## U
**UBO** (Ultimate Beneficial Owner). A natural person who ultimately owns or controls a legal entity, typically through ownership of 25% or more of shares or voting rights. Identifying UBOs is a core AML requirement under 5AMLD and FATF recommendations.
## W
**Webhook**. An HTTP callback that Zenoo sends to your server when an event occurs (verification completed, journey abandoned, check failed). Your server receives a POST request with the event payload and must respond with `200 OK` within 30 seconds.
**WorldCheck**. The Refinitiv (LSEG) screening database used by Zenoo for PEP, sanctions, adverse media, and watchlist screening. WorldCheck aggregates data from government sanctions lists (OFAC, HMT, EU), PEP databases, adverse media sources, and law enforcement watchlists.
## Next steps
* [Error Codes](/reference/error-codes) -- Machine-readable error codes
* [Country Codes](/reference/country-codes) -- ISO 3166-1 alpha-2 reference
* [Rate Limits](/reference/rate-limits) -- Per-endpoint rate limits
# Rate Limits
Source: https://docs.zenoo.com/reference/rate-limits
Per-endpoint rate limits, response headers, and best practices for request queuing and backoff.
# Rate Limits
Zenoo enforces rate limits per project to protect service stability. Limits apply independently to each API endpoint.
## Default limits
| Endpoint | Rate Limit | Description |
| - | - | - |
| `POST .../api` (sync) | 100 requests/minute | Company and Person sync verification |
| `POST .../init` (async) | 100 requests/minute | Async journey initiation |
| `GET .../sharable-payload/{token}` (pull) | 300 requests/minute | Result polling |
| `POST .../api` (screening) | 200 requests/minute | Standalone AML screening |
Limits are measured on a rolling 60-second window. Each project has its own independent counters.
Staging limits are lower: 50 requests/minute across all endpoints. Do not use staging for load testing.
## Rate limit response
When you exceed the limit, the API returns `429 Too Many Requests` with a `Retry-After` header:
**Response headers:**
```
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
```
**Response body:**
```json theme={null}
{
"error": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 30 seconds.",
"request_id": "req-a1b2c3d4"
}
```
The `Retry-After` value is in seconds. Always use this value instead of guessing.
## Best practices
### Implement request queuing
Don't rely on retry loops to handle rate limits. Use a queue to smooth out request bursts.
```javascript request-queue.js theme={null}
class RequestQueue {
constructor(maxPerMinute = 90) {
this.queue = [];
this.interval = (60 / maxPerMinute) * 1000;
this.processing = false;
}
enqueue(requestFn) {
return new Promise((resolve, reject) => {
this.queue.push({ requestFn, resolve, reject });
if (!this.processing) this.processQueue();
});
}
async processQueue() {
this.processing = true;
while (this.queue.length > 0) {
const { requestFn, resolve, reject } = this.queue.shift();
try {
const result = await requestFn();
resolve(result);
} catch (error) {
reject(error);
}
await new Promise((r) => setTimeout(r, this.interval));
}
this.processing = false;
}
}
// Usage
const queue = new RequestQueue(90); // Stay under 100/min limit
await queue.enqueue(() => fetch(zenooUrl, options));
```
Set `maxPerMinute` to 90% of your limit to leave headroom for retries.
### Respect the Retry-After header
When you receive a `429`, always wait for the duration specified in `Retry-After`. Do not guess or use a fixed delay.
```javascript theme={null}
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After") || "60");
await new Promise((r) => setTimeout(r, retryAfter * 1000));
// Retry the request
}
```
### Monitor queue depth
Track the size of your request queue over time. A growing queue indicates you're approaching your rate limit and may need a higher allocation.
```javascript theme={null}
setInterval(() => {
metrics.gauge("zenoo.queue_depth", queue.queue.length);
}, 10000);
```
If queue depth consistently exceeds 50 pending requests, contact Zenoo about increasing your limits.
### Use sync mode for batch operations
If you need to verify many entities at once, use sync mode with `X-SYNC-TIMEOUT` instead of initiating many async journeys. Sync requests consume fewer round trips than the init + poll pattern.
### Separate polling from submission
The pull endpoint (`/sharable-payload/{token}`) has a higher rate limit (300/min) than the submission endpoints (100/min). Polling aggressively will not count against your submission limit.
## Higher limits
Default limits are sufficient for most integrations. If you need higher throughput, contact your Zenoo account manager with your current request volume, expected peak volume, and use case (batch processing, real-time onboarding, ongoing monitoring). Higher limits are available on Enterprise plans.
## Next steps
* [Error Codes](/reference/error-codes) -- Complete error code reference including RATE\_LIMITED
* [Error Handling](/guides/error-handling) -- Retry strategies and exponential backoff
* [Country Codes](/reference/country-codes) -- Supported jurisdictions
# Status Enums
Source: https://docs.zenoo.com/reference/status-enums
Comprehensive reference of all enum values used across the Zenoo CLM API, organized by resource type.
# Status Enums
This page lists every enum value used in the Zenoo CLM API, organized by resource type. Use these exact values when filtering, creating, or updating resources.
## Alert Enums
### Alert Statuses
| Value | Description |
| - | - |
| `Open` | Created, awaiting analyst review |
| `Acknowledged` | Analyst has claimed the alert |
| `Resolved` | Resolved with an action and notes |
| `False Positive` | Confirmed as a false positive match |
| `Escalated` | Escalated to a senior reviewer or manager |
### Alert Categories
| Value | Type | Description |
| - | - | - |
| `PEP Match` | Screening | Politically Exposed Person match |
| `Sanctions Hit` | Screening | Sanctions list match |
| `Adverse Media` | Screening | Negative media coverage match |
| `Name Mismatch` | Identity | Name differs between documents |
| `DOB Mismatch` | Identity | Date of birth discrepancy |
| `Criminal Investigation` | Screening | Criminal investigation record |
| `Regulatory Action` | Screening | Regulatory enforcement action |
| `API Failure` | Company | External API check failure |
| `Watchlist Match` | Screening | Other watchlist match |
| `Ongoing Screening` | Screening | Hit from ongoing monitoring |
| `RCA Match` | Screening | Relative or close associate match |
### Alert Types
| Value | Description |
| - | - |
| `Screening` | Generated from PEP/sanctions/adverse media checks |
| `Identity` | Generated from identity verification failures |
| `Company` | Generated from company registry check issues |
| `Document` | Generated from document verification failures |
| `Financial` | Generated from financial or credit check issues |
| `Client Input` | Created when client input is required |
| `Internal Review` | Created for internal compliance review |
### Alert Priorities
| Value | Score Range | Description |
| - | - | - |
| `Critical` | 200+ | Immediate action required (e.g., sanctions) |
| `High` | 120-199 | Urgent review needed |
| `Medium` | 60-119 | Standard review |
| `Low` | 0-59 | Low urgency |
### Resolution Actions
| Value | Description |
| - | - |
| `Approve` | Alert reviewed, risk accepted |
| `Decline` | Confirmed compliance issue, reject entity |
| `Escalate` | Route to senior reviewer |
| `Request Document` | Request additional evidence from client |
| `Approve with Conditions` | Accept with monitoring or restrictions |
## Case Enums
### Case Statuses
| Value | Description |
| - | - |
| `New` | Case created, not yet started |
| `In Progress` | Analyst actively working |
| `In Review` | Submitted for peer or senior review |
| `Internal Review` | Under internal compliance review or escalation |
| `Client Input` | Waiting for client documents or information |
| `Closed` | Investigation complete (terminal state) |
| `Escalated` | Escalated to manager due to complexity or SLA breach |
### Case Types
| Value | Description |
| - | - |
| `Onboarding` | Initial customer due diligence during onboarding |
| `Review` | Periodic re-review of existing customers |
| `Perpetual` | Ongoing monitoring and continuous screening |
## Entity Enums
### Entity Categories
| Value | Description |
| - | - |
| `Company` | Legal entity (Ltd, LLC, GmbH, etc.) |
| `Person` | Natural person (individual) |
### Entity Roles
| Value | Description |
| - | - |
| `Primary` | Main entity under investigation |
| `UBO` | Ultimate Beneficial Owner (>25% ownership) |
| `Director` | Company director or officer |
| `Shareholder` | Non-UBO shareholder |
### Verification Statuses
| Value | Description |
| - | - |
| `Not Started` | No checks executed for this entity |
| `In Progress` | At least one check running or pending |
| `Verified` | All required checks passed |
| `Expired` | Previous verification exceeded validity period |
| `Failed` | One or more required checks failed |
## Check Enums
### Check Statuses
| Value | Description |
| - | - |
| `Pending` | Created, awaiting execution or manual action |
| `Queued` | Scheduled for automatic execution |
| `In Progress` | Currently executing against a provider |
| `Awaiting Document` | Hybrid check waiting for document upload |
| `Awaiting Client` | Waiting for client-side action |
| `Complete` | Execution finished with a result |
| `Failed` | Execution error or timeout |
| `Waived` | Manually waived by analyst |
| `Cancelled` | Cancelled before execution |
| `Reused` | Valid check from another case reused |
| `Retry Scheduled` | Failed check scheduled for retry |
### Check Categories
| Value | Description |
| - | - |
| `Screening` | PEP, sanctions, adverse media, watchlist checks |
| `Identity` | Identity and biometric verification |
| `Company` | Company registry and corporate checks |
| `Financial` | Credit, source of wealth, financial checks |
| `EDD` | Enhanced Due Diligence checks |
### Check Results
| Value | Description | Alert generated? |
| - | - | - |
| `Pass` | Verification passed, no issues | No |
| `Refer` | Potential issue, needs review | Yes |
| `Fail` | Verification failed | Yes |
## Risk Enums
### Risk Tiers
| Value | Score Range | Description |
| - | - | - |
| `High` | 70-100 | High risk, EDD required |
| `Medium` | 40-69 | Medium risk, enhanced monitoring |
| `Low` | 0-39 | Low risk, standard due diligence |
### Risk Assessment Statuses
| Value | Description |
| - | - |
| `Draft` | Assessment created, scores not yet calculated |
| `Active` | Scores calculated, awaiting review |
| `Superseded` | Replaced by a newer assessment |
| `Overridden` | Analyst manually overrode the calculated tier |
| `Approved` | Reviewed and approved (effective assessment) |
## SLA Enums
### SLA Statuses
| Value | Description |
| - | - |
| `On Track` | More than warning days remaining |
| `Warning` | Within warning threshold |
| `Critical` | Due date is today |
| `Breached` | Past due date |
## Comment Enums
### Comment Types
| Value | Description |
| - | - |
| `Comment` | Standard user comment |
| `Decision` | Compliance decision recorded as a comment |
| `Note` | Internal note |
| `System` | System-generated comment (automation, status changes) |
## Reviewer Enums
### Reviewer Statuses
| Value | Description |
| - | - |
| `Invited` | Reviewer invited, has not yet responded |
| `Reviewing` | Reviewer actively reviewing |
| `Approved` | Reviewer approved the item |
| `Declined` | Reviewer declined the item |
## Audit Log Enums
### Severity Levels
| Value | Description |
| - | - |
| `INFO` | Standard informational event |
| `WARNING` | Action requiring attention (escalations, overrides) |
| `ERROR` | Error condition (SLA breach, system failure) |
### Event Types
See [Audit Logs Data Model](/data-model/audit-logs) for the complete list of 32 event types with descriptions and key fields.
## Webhook Event Types
| Value | Category | Description |
| - | - | - |
| `case.created` | Case | New case created |
| `case.status_changed` | Case | Case status transition |
| `case.closed` | Case | Case closed |
| `case.escalated` | Case | Case escalated |
| `alert.created` | Alert | New alert generated |
| `alert.resolved` | Alert | Alert resolved |
| `alert.escalated` | Alert | Alert escalated |
| `alert.ai_research_completed` | Alert | AI research completed |
| `check.completed` | Check | Check execution completed |
| `check.failed` | Check | Check execution failed |
| `risk_assessment.completed` | Risk | Risk scoring completed |
| `risk_assessment.approved` | Risk | Assessment approved |
| `sla.warning` | SLA | SLA warning threshold reached |
| `sla.breached` | SLA | SLA deadline passed |
## See also
* [Alerts Data Model](/data-model/alerts)
* [Cases Data Model](/data-model/cases)
* [Checks Data Model](/data-model/checks)
* [Audit Logs Data Model](/data-model/audit-logs)
* [Webhook Events](/data-model/webhook-events)
# Go-Live Checklist
Source: https://docs.zenoo.com/testing/go-live-checklist
12-item pre-production checklist covering credentials, security, resilience, result handling, and monitoring.
# Go-Live Checklist
Complete every item before switching from staging to production. Each item addresses a specific failure mode observed in real integrations.
Do not go live until every item on this checklist is verified. Skipping items leads to production incidents that are entirely preventable.
## Credentials and endpoints
### 1. Replace staging API key with production key
Update your environment configuration to use the production API key. Staging keys do not work against the production URL.
```bash theme={null}
# Before (staging)
ZENOO_API_KEY=stg_your_staging_key
# After (production)
ZENOO_API_KEY=prod_your_production_key
```
### 2. Update base URL to production
Switch from the staging URL to the production URL. This must change everywhere your application makes Zenoo API calls.
```bash theme={null}
# Before (staging)
ZENOO_BASE_URL=https://instance.staging.onboardapp.io
# After (production)
ZENOO_BASE_URL=https://instance.prod.onboardapp.io
```
### 3. Update webhook endpoint to production URL
Point Zenoo at your production webhook endpoint. Contact your Zenoo account manager to update the webhook URL, or configure it in the Zenoo dashboard.
Staging and production webhooks use different secrets. Update the secret in your configuration.
## Security
### 4. Verify webhook signature validation works
Confirm that your webhook handler validates the `X-Zenoo-Signature` header on every request. Test by sending a webhook with a tampered payload and verifying that your handler rejects it.
```javascript theme={null}
const crypto = require("crypto");
function verifySignature(rawBody, signature, secret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
// Reject unverified webhooks
if (
!verifySignature(rawBody, req.headers["x-zenoo-signature"], webhookSecret)
) {
return res.status(401).json({ error: "Invalid signature" });
}
```
## Resilience
### 5. Implement exponential backoff retry logic
Your API client should retry 5xx errors and timeouts with increasing delays. Never retry 4xx errors without fixing the request.
```javascript theme={null}
const RETRY_DELAYS = [0, 5000, 25000, 60000]; // 4 attempts max
async function callZenoo(requestFn) {
for (let attempt = 0; attempt < RETRY_DELAYS.length; attempt++) {
if (attempt > 0) await sleep(RETRY_DELAYS[attempt]);
const response = await requestFn();
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After") || "60");
await sleep(retryAfter * 1000);
continue;
}
if (response.status >= 500 && attempt < RETRY_DELAYS.length - 1) {
continue;
}
return response;
}
}
```
### 6. Set up idempotency with external\_reference
Include `external_reference` on every verification request. This prevents duplicate verifications caused by retries, user double-clicks, or race conditions.
```json theme={null}
{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"external_reference": "your-unique-internal-id-here"
}
```
Use a stable, unique identifier from your system: application ID, customer reference, or order number. If the same `external_reference` is submitted twice, Zenoo returns the existing case.
## Result handling
### 7. Handle all verdict outcomes
Your application must handle all three `overall_verdict` values. Verify your decision logic:
| Verdict | Required action |
| - | - |
| `Pass` | Proceed with onboarding or approval |
| `Refer` | Route to manual compliance review |
| `Fail` | Block onboarding, escalate to compliance team |
Do not assume all results will be `Pass`. Test your `Refer` and `Fail` code paths with the [staging test data](/testing/test-data).
### 8. Handle webhook edge cases
Your webhook handler must process all five event types, not just `verification.completed`:
| Event | Required action |
| - | - |
| `verification.completed` | Process results, update customer record |
| `screening.completed` | Review matches, update risk profile |
| `journey.abandoned` | Send reminder or create a new journey |
| `journey.expired` | Create a new verification journey |
| `check.failed` | Alert compliance team for manual intervention |
Ignoring `journey.abandoned` or `journey.expired` events causes customers to get stuck in your onboarding flow.
### 9. Implement webhook deduplication
Zenoo may deliver the same webhook more than once. Your handler must be idempotent.
```javascript theme={null}
app.post("/webhooks/zenoo", async (req, res) => {
const { journey_id, event_type } = req.body;
// Check for duplicate delivery
const existing = await db.webhookEvents.findOne({ journey_id, event_type });
if (existing) {
return res.status(200).json({ received: true });
}
// Store before processing to prevent race conditions
await db.webhookEvents.create({ journey_id, event_type, payload: req.body });
await processEvent(req.body);
res.status(200).json({ received: true });
});
```
Always return `200` for duplicates to prevent unnecessary retries.
## Monitoring
### 10. Set up monitoring
Track these metrics from day one:
| Metric | Alert threshold |
| - | - |
| API error rate (5xx) | > 5% over 10 minutes |
| Webhook delivery failures | > 3 consecutive failures |
| Average response time | > 30 seconds (sync mode) |
| Verification completion rate | \< 90% over 1 hour |
| Sanctions hit rate | Sudden spike (> 2x normal) |
Use the `request_id` from error responses to correlate application logs with Zenoo server logs.
### 11. Test one real verification in production
After completing items 1-10, run a single real verification in production. Use a real company or individual (not test data). Confirm:
* API call succeeds with `200` response
* Response contains valid `overall_verdict` and `risk_tier`
* Webhook is delivered to your production endpoint
* Webhook signature verifies correctly
* Your application processes the result correctly
### 12. Configure alerts for failures
Set up automated alerts for:
* **5xx error spikes** from the Zenoo API. These indicate server-side issues, contact Zenoo support.
* **Webhook delivery failures.** If your endpoint returns non-2xx for more than 3 consecutive webhooks, investigate immediately.
* **Verification timeout increases.** A sustained increase in response times may indicate provider degradation.
* **Unusual verdict distributions.** A sudden spike in `Fail` verdicts may indicate a configuration issue, not actual compliance failures.
## Summary
Complete all 12 items before going live. Each item addresses a real production failure mode.
| # | Item | Status |
| - | - | - |
| 1 | Replace staging API key with production key | \[ ] |
| 2 | Update base URL to production | \[ ] |
| 3 | Update webhook endpoint to production URL | \[ ] |
| 4 | Verify webhook signature validation works | \[ ] |
| 5 | Implement exponential backoff retry logic | \[ ] |
| 6 | Set up idempotency with external\_reference | \[ ] |
| 7 | Handle all verdict outcomes (Pass, Refer, Fail) | \[ ] |
| 8 | Handle webhook edge cases (abandoned, expired, failed) | \[ ] |
| 9 | Implement webhook deduplication | \[ ] |
| 10 | Set up monitoring (error rates, webhooks, response times) | \[ ] |
| 11 | Test one real verification in production | \[ ] |
| 12 | Configure alerts for 5xx spikes and webhook failures | \[ ] |
## Next steps
* [Error Handling](/guides/error-handling) -- Retry strategies and circuit breaker behavior
* [Webhooks Guide](/guides/webhooks) -- Webhook event types and delivery
* [Reference: Error Codes](/reference/error-codes) -- Complete error code reference
# Integration Scenarios
Source: https://docs.zenoo.com/testing/integration-scenarios
End-to-end test scripts for validating your Zenoo integration against the staging environment.
# Integration Scenarios
End-to-end test scripts for validating your Zenoo integration. Run these against the staging environment before going live.
Replace `{project_hash}` with your staging project hash and `staging-key` with your staging API key in all examples below.
## Scenario 1: Company Verification sync end-to-end
Submit a company for verification using sync mode and validate the response.
```bash theme={null}
# Submit company verification (sync)
curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{
"company_name": "Test Company Clean Ltd",
"registration_number": "TEST001",
"country": "GB",
"external_reference": "e2e-kyb-sync-001"
}' | jq .
```
**Verify the response contains:**
* `overall_verdict` is `"Pass"`, `"Refer"`, or `"Fail"`
* `risk_tier` is `"Low"`, `"Medium"`, or `"High"`
* `company.legal_name` is populated
* `company.verification_status` is `"Verified"`
* `screening` object contains `pep_status`, `sanctions_status`, `adverse_media_status`
* `checks_summary.total` is greater than 0
* `case_reference` starts with `"AML-"`
```bash theme={null}
# Validate response fields
RESPONSE=$(curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{"company_name":"Test Company Clean Ltd","registration_number":"TEST001","country":"GB","external_reference":"e2e-kyb-sync-001"}')
echo "$RESPONSE" | jq '{
verdict: .overall_verdict,
risk: .risk_tier,
company_verified: .company.verification_status,
checks_total: .checks_summary.total,
pep: .screening.pep_status,
sanctions: .screening.sanctions_status
}'
```
## Scenario 2: Person Verification async end-to-end
Initiate an async Person Verification journey, extract tokens, and poll for results.
```bash theme={null}
# Step 1: Initiate async journey
INIT_RESPONSE=$(curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/init" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-d '{
"first_name": "Test",
"last_name": "Clean",
"date_of_birth": "1990-01-15",
"country": "GB",
"external_reference": "e2e-kyc-async-001"
}')
echo "Init response:"
echo "$INIT_RESPONSE" | jq .
# Step 2: Extract pull token
PULL_TOKEN=$(echo "$INIT_RESPONSE" | jq -r '.tokens.pull')
echo "Pull token: $PULL_TOKEN"
# Step 3: Poll for results (may return 204 while processing)
sleep 10
RESULT=$(curl -s -w "\n%{http_code}" \
"https://instance.staging.onboardapp.io/api/gateway/sharable-payload/$PULL_TOKEN")
HTTP_CODE=$(echo "$RESULT" | tail -1)
BODY=$(echo "$RESULT" | head -n -1)
if [ "$HTTP_CODE" = "200" ]; then
echo "Results ready:"
echo "$BODY" | jq .
elif [ "$HTTP_CODE" = "204" ]; then
echo "Still processing. Poll again in 30 seconds."
elif [ "$HTTP_CODE" = "404" ]; then
echo "Token invalid or expired."
fi
```
**Verify:**
* Init response contains `tokens.pull` and `tokens.start`
* Poll returns `204` (processing) or `200` (results ready)
* Final `200` response contains `overall_verdict` and `risk_tier`
## Scenario 3: Idempotency
Submit the same `external_reference` twice. The second request should return the existing case, not create a duplicate.
```bash theme={null}
REFERENCE="e2e-idempotent-$(date +%s)"
# First request
echo "--- First request ---"
curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d "{
\"company_name\": \"Test Company Clean Ltd\",
\"registration_number\": \"TEST001\",
\"country\": \"GB\",
\"external_reference\": \"$REFERENCE\"
}" | jq '{case_reference: .case_reference, verdict: .overall_verdict}'
# Second request (same external_reference)
echo "--- Second request ---"
curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d "{
\"company_name\": \"Test Company Clean Ltd\",
\"registration_number\": \"TEST001\",
\"country\": \"GB\",
\"external_reference\": \"$REFERENCE\"
}" | jq '{case_reference: .case_reference, verdict: .overall_verdict}'
```
**Verify:**
* Both responses return the same `case_reference`
* No duplicate verification is created
* The second request completes faster (returns cached result)
## Scenario 4: Error handling
Test error responses for common failure modes.
### Missing required field (400)
```bash theme={null}
curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 15000" \
-d '{"country": "GB"}' | jq .
```
**Expected:** `400` with `error: "VALIDATION_ERROR"`.
### Invalid API key (401)
```bash theme={null}
curl -s -w "\nHTTP %{http_code}" -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: invalid-key-here" \
-H "X-SYNC-TIMEOUT: 15000" \
-d '{"company_name":"Test","registration_number":"TEST001","country":"GB"}'
```
**Expected:** `401` with `error: "UNAUTHORIZED"`.
### Invalid project hash (404)
```bash theme={null}
curl -s -w "\nHTTP %{http_code}" -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/invalid-hash/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 15000" \
-d '{"company_name":"Test","registration_number":"TEST001","country":"GB"}'
```
**Expected:** `404` with `error: "NOT_FOUND"`.
### Invalid JSON (400)
```bash theme={null}
curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-d 'this is not json' | jq .
```
**Expected:** `400` with `error: "INVALID_JSON"`.
## Scenario 5: Webhook delivery
Submit a verification and confirm your webhook endpoint receives the event.
### Setup
Before running this test, ensure your staging webhook URL is configured. For local development:
```bash theme={null}
# Terminal 1: Start your webhook listener
node -e "
const http = require('http');
const server = http.createServer((req, res) => {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
console.log('Webhook received:', JSON.stringify(JSON.parse(body), null, 2));
res.writeHead(200);
res.end('OK');
});
});
server.listen(3000, () => console.log('Listening on port 3000'));
"
# Terminal 2: Expose via ngrok
ngrok http 3000
# Note the https URL and configure it as your staging webhook endpoint
```
### Submit verification
```bash theme={null}
# Submit a company verification to trigger a webhook
curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{
"company_name": "Test Company Clean Ltd",
"registration_number": "TEST001",
"country": "GB",
"external_reference": "e2e-webhook-001"
}'
```
### Verify webhook received
Check your webhook listener for a `verification.completed` event. The payload should include:
* `event_type: "verification.completed"`
* `callback_reference` matching your `external_reference`
* `data` object with the full verification results
* `X-Zenoo-Signature` header for signature verification
### Verify signature
```javascript theme={null}
const crypto = require("crypto");
function verifyWebhookSignature(payload, signature, secret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(payload).digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
```
## Checklist
Run all five scenarios and confirm:
* [ ] Scenario 1: Sync Company Verification returns a complete compliance report
* [ ] Scenario 2: Async Person Verification init returns tokens, poll returns results
* [ ] Scenario 3: Duplicate `external_reference` returns same case
* [ ] Scenario 4: All four error cases return correct status codes
* [ ] Scenario 5: Webhook received with valid signature
## Next steps
* [Go-Live Checklist](/testing/go-live-checklist) -- Pre-production requirements
* [Test Data](/testing/test-data) -- Additional test payloads
* [Error Handling](/guides/error-handling) -- Production error handling patterns
# Staging Environment
Source: https://docs.zenoo.com/testing/staging-environment
Dedicated staging environment that mirrors the production API with mock providers for integration testing.
# Staging Environment
Zenoo provides a full staging environment for integration testing. Staging mirrors the production API surface, including webhook delivery, but uses mock providers instead of real verification services. You can test every flow, error path, and edge case without consuming production credits or triggering real compliance checks.
**What staging gives you:**
* **Same API endpoints** as production, with a different base URL and separate credentials
* **Mock providers** that return simulated results based on pattern-matched test data
* **Full webhook delivery** with the same event types and payload structure as production
* **Lower rate limits** to prevent accidental resource exhaustion during development
## Staging vs production
Staging and production are completely isolated. A staging API key will not work against the production URL, and vice versa. Always ensure your environment configuration matches the target environment.
| Aspect | Staging | Production |
| - | - | - |
| **Base URL** | `https://instance.staging.onboardapp.io` | `https://instance.prod.onboardapp.io` |
| **API keys** | Separate staging keys | Production keys |
| **Providers** | Mock/sandbox providers | Live provider APIs (registry, screening providers) |
| **Webhooks** | Full delivery, same event types | Full delivery |
| **Rate limits** | 50 requests/minute | 100-300 requests/minute (varies by endpoint) |
| **Data retention** | Periodic purge (30 days) | Full retention per contract |
| **Charges** | None | Per-verification pricing |
## Getting started
During onboarding, Zenoo provides:
* A staging API key
* A staging project hash
* A webhook secret for signature verification
If you do not have these, contact your Zenoo account manager or email [support@zenoo.com](mailto:support@zenoo.com).
Store your staging credentials separately from production. A common pattern:
```bash .env.staging theme={null}
ZENOO_BASE_URL=https://instance.staging.onboardapp.io
ZENOO_API_KEY=stg_your_staging_key_here
ZENOO_PROJECT_HASH=abc123def456
ZENOO_WEBHOOK_SECRET=whsec_staging_secret_here
```
```bash .env.production theme={null}
ZENOO_BASE_URL=https://instance.prod.onboardapp.io
ZENOO_API_KEY=prod_your_production_key_here
ZENOO_PROJECT_HASH=xyz789ghi012
ZENOO_WEBHOOK_SECRET=whsec_production_secret_here
```
Point at the staging URL and send a request:
```bash theme={null}
curl -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: stg_your_staging_key_here" \
-H "X-SYNC-TIMEOUT: 15000" \
-d '{
"name": "Test Clean Person",
"date_of_birth": "1990-01-15",
"country": "GB",
"entity_type": "person",
"categories": ["pep", "sanctions", "adverse_media"]
}'
```
You should receive a `200` response with `pep_status: "No Hit"`, `sanctions_status: "No Hit"`, and `adverse_media_status: "No Hit"`.
## Mock provider behavior
Staging providers use pattern-matched test names to determine outcomes. The mock system recognizes specific strings in the submitted data and returns corresponding results.
| Pattern in name | Result |
| - | - |
| `Clean` | Pass, no screening hits |
| `PEPMatch` | Refer, PEP match returned |
| `SanctionsHit` | Fail, sanctions hit returned |
| `AdverseMedia` | Refer, adverse media matches |
| `INVALID999` (registration number) | Fail, company not found in registry |
| `TEST001` (registration number) | Pass, clean company registry result |
| `TEST002` (registration number) | Refer, company with screening hits |
Names that don't match any pattern default to a clean pass. Pattern matching is case-insensitive.
Mock results include realistic response structures: match scores, screening provider names, categories, and timestamps. The data looks like a real response, but the values are synthetic.
### What mock providers do not do
* No real screening against WorldCheck, OFAC, HMT, or any live database
* No real company registry lookups
* No real document verification or biometric liveness checks
* No real phone, email, or address verification
## Webhook behavior in staging
Staging delivers webhooks with the same event types and payload structure as production:
* `verification.completed`
* `screening.completed`
* `journey.abandoned`
* `journey.expired`
* `check.failed`
Configure your staging webhook URL during onboarding. For local development, use a tunneling tool:
```bash theme={null}
ngrok http 3000
# Configure webhook URL: https://abc123.ngrok.io/webhooks/zenoo
```
Webhook signature verification works identically in staging. Use your staging webhook secret.
## Limitations
Be aware of the following staging limitations:
* **Mock providers.** Staging results are simulated. They test your integration logic, not the accuracy of real providers. Always validate with at least one real verification in production before going live.
* **No real screening.** PEP, sanctions, and adverse media results are pattern-matched, not sourced from live databases. Do not use staging results for compliance decisions.
* **Lower rate limits.** Staging enforces 50 requests per minute (vs 100-300 in production). This is sufficient for integration testing but not for load testing.
* **Periodic data purge.** Staging data is purged every 30 days. Do not store long-lived references to staging cases or verification results.
* **No SLA.** Staging does not have an uptime SLA. Planned maintenance windows may cause brief unavailability.
## Next steps
* [Test Data](/testing/test-data) -- Pre-built payloads for every scenario
* [Integration Scenarios](/testing/integration-scenarios) -- End-to-end test scripts
* [Go-Live Checklist](/testing/go-live-checklist) -- Pre-production requirements
# Test Data
Source: https://docs.zenoo.com/testing/test-data
Pre-built payloads for testing every verification outcome in the Zenoo staging environment.
# Test Data
Pre-built payloads for testing every verification outcome in the staging environment. Each payload triggers a specific result from mock providers.
Always include `external_reference` in your test payloads. This enables idempotency testing and correlates requests with your internal records.
## Company Verification
### Clean company (Pass)
```json theme={null}
{
"company_name": "Test Company Clean Ltd",
"registration_number": "TEST001",
"country": "GB",
"company_type": "Private Limited Company (Ltd)",
"incorporation_date": "2020-01-15",
"external_reference": "test-kyb-clean-001"
}
```
**Expected result:**
```json theme={null}
{
"overall_verdict": "Pass",
"risk_tier": "Low",
"company": {
"legal_name": "Test Company Clean Ltd",
"registration_number": "TEST001",
"company_status": "Active",
"verification_status": "Verified"
},
"screening": {
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "No Hit"
},
"checks_summary": {
"total": 3,
"passed": 3,
"referred": 0,
"failed": 0
}
}
```
### Company with screening hits (Refer)
```json theme={null}
{
"company_name": "Test Company Screening Ltd",
"registration_number": "TEST002",
"country": "GB",
"external_reference": "test-kyb-refer-002",
"directors": [
{
"first_name": "Test",
"last_name": "PEPMatch",
"date_of_birth": "1970-01-01",
"nationality": "GB"
}
]
}
```
**Expected result:**
```json theme={null}
{
"overall_verdict": "Refer",
"risk_tier": "Medium",
"screening": {
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "No Hit"
},
"directors": [
{
"name": "Test PEPMatch",
"pep_status": "Hit",
"sanctions_status": "No Hit"
}
],
"checks_summary": {
"total": 4,
"passed": 3,
"referred": 1,
"failed": 0
}
}
```
The `Refer` verdict is triggered by the PEP match on the director. The company itself screens clean.
### Company registry failure (Fail)
```json theme={null}
{
"company_name": "Test Company NotFound Ltd",
"registration_number": "INVALID999",
"country": "GB",
"external_reference": "test-kyb-fail-003"
}
```
**Expected result:**
```json theme={null}
{
"overall_verdict": "Fail",
"risk_tier": "High",
"company": {
"verification_status": "Failed"
},
"checks_summary": {
"total": 2,
"passed": 0,
"referred": 0,
"failed": 2
}
}
```
The mock registry returns "company not found" for `INVALID999`. Use this to test how your application handles verification failures.
## Person Verification
### Clean individual (Pass)
```json theme={null}
{
"first_name": "Test",
"last_name": "Clean",
"date_of_birth": "1990-01-15",
"email": "test.clean@example.com",
"phone": "+447700900001",
"country": "GB",
"external_reference": "test-kyc-clean-001"
}
```
**Expected result:**
```json theme={null}
{
"overall_verdict": "Pass",
"risk_tier": "Low",
"identity": {
"verified": true,
"decision": "Pass",
"score": 95
},
"screening": {
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "No Hit"
}
}
```
### Individual with PEP match (Refer)
```json theme={null}
{
"first_name": "Test",
"last_name": "PEPMatch",
"date_of_birth": "1970-06-15",
"country": "GB",
"external_reference": "test-kyc-pep-002"
}
```
**Expected result:**
```json theme={null}
{
"overall_verdict": "Refer",
"risk_tier": "Medium",
"screening": {
"pep_status": "Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "No Hit"
}
}
```
The PEP match triggers a `Refer` verdict. Your application should route this to a compliance reviewer.
### Individual with sanctions hit (Fail)
```json theme={null}
{
"first_name": "Test",
"last_name": "SanctionsHit",
"date_of_birth": "1965-03-20",
"country": "RU",
"external_reference": "test-kyc-sanctions-003"
}
```
**Expected result:**
```json theme={null}
{
"overall_verdict": "Fail",
"risk_tier": "High",
"screening": {
"pep_status": "No Hit",
"sanctions_status": "Hit",
"adverse_media_status": "No Hit"
}
}
```
Sanctions hits produce an immediate `Fail` verdict. Your application should halt onboarding and escalate to your compliance team.
## Screening
### Clean screening
```json theme={null}
{
"name": "Test Clean Person",
"date_of_birth": "1990-01-15",
"country": "GB",
"entity_type": "person",
"categories": ["pep", "sanctions", "adverse_media"]
}
```
**Expected result:**
```json theme={null}
{
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "No Hit",
"screening_provider": "WorldCheck",
"matches": []
}
```
### Screening with matches
```json theme={null}
{
"name": "Test PEP Person",
"date_of_birth": "1970-06-15",
"country": "GB",
"entity_type": "person",
"categories": ["pep", "sanctions"]
}
```
**Expected result:**
```json theme={null}
{
"pep_status": "Hit",
"sanctions_status": "No Hit",
"screening_provider": "WorldCheck",
"matches": [
{
"matchScore": 92,
"primaryName": "Test PEP Person",
"category": "pep",
"categories": ["pep-class-2"],
"sources": ["WorldCheck"],
"nationality": "GB"
}
]
}
```
## Important notes
* **Staging results are mock data.** The match scores, categories, and sources are synthetic. They test your integration logic, not real screening accuracy.
* **Pattern matching is case-insensitive.** `PEPMatch`, `pepmatch`, and `PEPMATCH` all trigger the same PEP hit.
* **Names that don't match a pattern default to clean.** Any name without a recognized trigger string returns a Pass with no screening hits.
## Next steps
* [Integration Scenarios](/testing/integration-scenarios) -- End-to-end test scripts using this data
* [Staging Environment](/testing/staging-environment) -- Environment setup and configuration
* [Go-Live Checklist](/testing/go-live-checklist) -- Pre-production requirements
# Alert Review
Source: https://docs.zenoo.com/use-cases/alert-review
Triage and resolve AML alerts via the API using priority-based queuing, AI research, and bulk operations.
# Alert Review
This guide demonstrates how to triage and resolve AML alerts through the API. You will list open alerts by priority, inspect a specific alert, trigger AI-powered research, review the AI assessment, and resolve the alert.
## Prerequisites
* A valid API key (see [Authentication](/getting-started/authentication))
* At least one case with open alerts (see [End-to-End Onboarding](/use-cases/onboarding))
Retrieve all open alerts sorted by priority score (highest first). This mirrors the analyst inbox view.
```bash curl theme={null}
curl -X GET "https://api.zenoo.com/v1/alerts?status=Open&sort=-priority_score&limit=25" \
-H "Authorization: Bearer your-api-key"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.zenoo.com/v1/alerts",
headers={"Authorization": "Bearer your-api-key"},
params={
"status": "Open",
"sort": "-priority_score",
"limit": 25,
},
)
alerts = response.json()
for alert in alerts["data"]:
print(
f"[{alert['priority']}] {alert['category']}: {alert['title']} "
f"(score: {alert['priority_score']}, SLA: {alert['sla_status']})"
)
```
```javascript Node.js theme={null}
const params = new URLSearchParams({
status: "Open",
sort: "-priority_score",
limit: "25",
});
const response = await fetch(
`https://api.zenoo.com/v1/alerts?${params}`,
{ headers: { Authorization: "Bearer your-api-key" } }
);
const alerts = await response.json();
alerts.data.forEach((a) =>
console.log(
`[${a.priority}] ${a.category}: ${a.title} (score: ${a.priority_score}, SLA: ${a.sla_status})`
)
);
```
**Response:**
```json theme={null}
{
"data": [
{
"token": "alt_001",
"type": "Screening",
"category": "Sanctions Hit",
"title": "Sanctions Hit: Global Corp LLC",
"status": "Open",
"priority": "Critical",
"priority_score": 95,
"match_score": 88,
"sla_status": "On Track",
"sla_due_date": "2026-02-17T10:00:00Z",
"case_token": "cas_abc123",
"entity_name": "Global Corp LLC"
},
{
"token": "alt_002",
"type": "Screening",
"category": "PEP Match",
"title": "PEP Match: Maria Garcia",
"status": "Open",
"priority": "High",
"priority_score": 78,
"match_score": 72,
"sla_status": "On Track",
"sla_due_date": "2026-02-18T10:00:00Z",
"case_token": "cas_def456",
"entity_name": "Maria Garcia"
}
],
"meta": {
"total": 2,
"page": { "cursor": null, "has_more": false }
}
}
```
Use the `sort` parameter with a `-` prefix for descending order. Common sort fields: `priority_score`, `created_at`, `sla_due_date`.
### Filtering options
| Parameter | Type | Description |
| - | - | - |
| `status` | string | Filter by status: `Open`, `Acknowledged`, `Resolved`, `False Positive`, `Escalated` |
| `category` | string | Filter by category: `PEP Match`, `Sanctions Hit`, `Adverse Media`, etc. |
| `type` | string | Filter by type: `Screening`, `Identity`, `Company`, `Document`, `Financial` |
| `priority` | string | Filter by priority: `Critical`, `High`, `Medium`, `Low` |
| `case_token` | string | Filter alerts belonging to a specific case |
| `sort` | string | Sort field with optional `-` prefix for descending |
| `limit` | number | Results per page (default 25, max 100) |
| `cursor` | string | Pagination cursor from previous response |
Retrieve the full details of the highest-priority alert, including screening match data and AI fields.
```bash curl theme={null}
curl -X GET "https://api.zenoo.com/v1/alerts/alt_001" \
-H "Authorization: Bearer your-api-key"
```
```python Python theme={null}
response = requests.get(
"https://api.zenoo.com/v1/alerts/alt_001",
headers={"Authorization": "Bearer your-api-key"},
)
alert = response.json()
print(f"Category: {alert['category']}")
print(f"Match Score: {alert['match_score']}%")
print(f"Description: {alert['description']}")
```
```javascript Node.js theme={null}
const alertRes = await fetch("https://api.zenoo.com/v1/alerts/alt_001", {
headers: { Authorization: "Bearer your-api-key" },
});
const alert = await alertRes.json();
console.log(`Category: ${alert.category}`);
console.log(`Match Score: ${alert.match_score}%`);
console.log(`Description: ${alert.description}`);
```
**Response:**
```json theme={null}
{
"token": "alt_001",
"type": "Screening",
"category": "Sanctions Hit",
"title": "Sanctions Hit: Global Corp LLC",
"description": "WorldCheck screening returned a potential sanctions match against OFAC SDN list for entity 'Global Corp LLC'.",
"status": "Open",
"priority": "Critical",
"priority_score": 95,
"match_score": 88,
"risk_tier": "High",
"sla_status": "On Track",
"sla_due_date": "2026-02-17T10:00:00Z",
"case_token": "cas_abc123",
"entity_name": "Global Corp LLC",
"source_data": {
"provider": "WorldCheck",
"list_name": "OFAC SDN",
"matched_name": "Global Corporation LLC",
"match_type": "Exact"
},
"ai_research": null,
"created_at": "2026-02-16T09:00:00Z"
}
```
Request AI-powered analysis of the alert. The AI reviews the screening match against public sources to assess whether it is a true positive or false positive.
```bash curl theme={null}
curl -X POST "https://api.zenoo.com/v1/alerts/alt_001/ai-research" \
-H "Authorization: Bearer your-api-key"
```
```python Python theme={null}
response = requests.post(
"https://api.zenoo.com/v1/alerts/alt_001/ai-research",
headers={"Authorization": "Bearer your-api-key"},
)
result = response.json()
print(f"Status: {result['status']}") # processing
```
```javascript Node.js theme={null}
const aiRes = await fetch(
"https://api.zenoo.com/v1/alerts/alt_001/ai-research",
{
method: "POST",
headers: { Authorization: "Bearer your-api-key" },
}
);
const aiResult = await aiRes.json();
console.log(`Status: ${aiResult.status}`); // processing
```
**Response:**
```json theme={null}
{
"status": "processing",
"message": "AI research initiated. Results will be available shortly.",
"estimated_seconds": 15
}
```
AI research runs asynchronously. Typical completion time is 10-30 seconds. You will receive a `alert.ai_research_completed` webhook event when results are ready, or you can poll the endpoint below.
Retrieve the AI assessment once processing completes.
```bash curl theme={null}
curl -X GET "https://api.zenoo.com/v1/alerts/alt_001/ai-research" \
-H "Authorization: Bearer your-api-key"
```
```python Python theme={null}
response = requests.get(
"https://api.zenoo.com/v1/alerts/alt_001/ai-research",
headers={"Authorization": "Bearer your-api-key"},
)
research = response.json()
print(f"Assessment: {research['assessment']}")
print(f"Confidence: {research['confidence']}%")
print(f"False Positive Probability: {research['false_positive_probability']}%")
print(f"Recommended Action: {research['recommended_action']}")
```
```javascript Node.js theme={null}
const researchRes = await fetch(
"https://api.zenoo.com/v1/alerts/alt_001/ai-research",
{ headers: { Authorization: "Bearer your-api-key" } }
);
const research = await researchRes.json();
console.log(`Assessment: ${research.assessment}`);
console.log(`Confidence: ${research.confidence}%`);
console.log(`FP Probability: ${research.false_positive_probability}%`);
console.log(`Recommended: ${research.recommended_action}`);
```
**Response:**
```json theme={null}
{
"status": "completed",
"assessment": "The matched entity 'Global Corporation LLC' on the OFAC SDN list is a different legal entity registered in Iran. The subject entity 'Global Corp LLC' is incorporated in Delaware, USA with no apparent connection to the sanctioned entity beyond name similarity.",
"confidence": 85,
"false_positive_probability": 82,
"recommended_action": "Approve",
"sources": [
{ "title": "OFAC SDN List", "url": "https://sanctionssearch.ofac.treas.gov/" },
{ "title": "Delaware Division of Corporations", "url": "https://icis.corp.delaware.gov/" }
],
"researched_at": "2026-02-16T09:01:15Z"
}
```
Based on the AI assessment and your review, resolve the alert with an action and notes.
```bash curl theme={null}
curl -X POST "https://api.zenoo.com/v1/alerts/alt_001/resolve" \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"action": "Approve",
"notes": "False positive confirmed. Matched entity is Global Corporation LLC (Iran), unrelated to subject Global Corp LLC (Delaware, USA). AI confidence 85%. No sanctions exposure."
}'
```
```python Python theme={null}
response = requests.post(
"https://api.zenoo.com/v1/alerts/alt_001/resolve",
headers={
"Authorization": "Bearer your-api-key",
"Content-Type": "application/json",
},
json={
"action": "Approve",
"notes": "False positive confirmed. Matched entity is Global Corporation LLC (Iran), unrelated to subject Global Corp LLC (Delaware, USA). AI confidence 85%. No sanctions exposure.",
},
)
print(response.json()["status"]) # Resolved
```
```javascript Node.js theme={null}
const resolveRes = await fetch(
"https://api.zenoo.com/v1/alerts/alt_001/resolve",
{
method: "POST",
headers: {
Authorization: "Bearer your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
action: "Approve",
notes:
"False positive confirmed. Matched entity is Global Corporation LLC (Iran), unrelated to subject Global Corp LLC (Delaware, USA). AI confidence 85%. No sanctions exposure.",
}),
}
);
const resolved = await resolveRes.json();
console.log(resolved.status); // Resolved
```
**Response:**
```json theme={null}
{
"token": "alt_001",
"status": "Resolved",
"resolution_action": "Approve",
"resolved_by": "analyst@example.com",
"resolved_at": "2026-02-16T09:05:00Z"
}
```
### Resolution actions
| Action | When to use |
| - | - |
| `Approve` | Alert reviewed, no issues found or risk accepted |
| `Decline` | Confirmed compliance issue, reject the entity |
| `Escalate` | Uncertain or complex, route to senior reviewer |
| `Request Document` | Additional evidence needed from the client |
| `Approve with Conditions` | Approved with caveats (e.g., enhanced monitoring) |
If a sanctions hit is confirmed as a true positive, use `Decline` and escalate the case immediately. Processing sanctioned entities may violate legal obligations.
## Alert lifecycle
Alerts move through a defined set of statuses from creation to resolution:
```mermaid theme={null}
stateDiagram-v2
[*] --> Open : Alert created
Open --> Acknowledged : Analyst claims
Open --> Resolved : Direct resolution
Open --> False_Positive : Confirmed FP
Open --> Escalated : Escalate to manager
Acknowledged --> Resolved : After review
Acknowledged --> False_Positive : Confirmed FP
Acknowledged --> Escalated : Escalate
Resolved --> [*]
False_Positive --> [*]
Escalated --> [*]
```
| Status | Description |
| - | - |
| `Open` | Alert created, awaiting analyst review |
| `Acknowledged` | Analyst has claimed and is reviewing the alert |
| `Resolved` | Alert reviewed and resolved with an action |
| `False Positive` | Confirmed as a false positive match |
| `Escalated` | Escalated to a senior reviewer or manager |
## Priority scoring
Every alert receives a composite priority score (0-290 points) calculated from five components. The priority score drives the default sort order in the analyst inbox: highest-score alerts appear first.
| Component | Max Points | Calculation |
| - | - | - |
| SLA urgency | 100 | Breached = 100, linear decay from due date |
| Risk tier | 100 | Critical = 100, High = 75, Medium = 50, Low = 25 |
| Category weight | 60 | Sanctions = 60, PEP = 45, Adverse Media = 30 |
| Match score | 10 | Screening confidence / 10 |
| Case risk | 20 | Parent case risk score / 5 |
### Priority labels
| Priority | Score Range |
| - | - |
| `Critical` | 200+ |
| `High` | 120-199 |
| `Medium` | 60-119 |
| `Low` | 0-59 |
## Bulk operations
For high-volume alert processing, use the bulk endpoints. See [Bulk Operations Guide](/guides/bulk-operations) for details.
```bash theme={null}
curl -X POST "https://api.zenoo.com/v1/alerts/bulk/resolve" \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"alert_tokens": ["alt_003", "alt_004", "alt_005"],
"action": "Approve",
"notes": "Batch resolved: low-risk name mismatches confirmed as formatting differences."
}'
```
## Next steps
* [Alert Management Guide](/guides/alert-management). Deep dive into the alert lifecycle and priority scoring.
* [AI Research Guide](/guides/ai-research). Learn about AI-powered analysis and auto-disposition.
* [Bulk Operations Guide](/guides/bulk-operations). Process alerts at scale.
* [Pagination Guide](/guides/pagination). Navigate large result sets efficiently.
# Compliance Reports
Source: https://docs.zenoo.com/use-cases/compliance-reports
Generate and interpret a compliance report for a completed case via the API.
# Compliance Reports
This guide shows how to generate a compliance report once all checks and alerts for a case have been resolved. The report provides a structured summary suitable for regulatory filing and internal audit.
## Prerequisites
* A valid API key (see [Authentication](/getting-started/authentication))
* A case with all alerts resolved (see [End-to-End Onboarding](/use-cases/onboarding))
Retrieve the case to confirm its current status and check progress.
```bash curl theme={null}
curl -X GET "https://api.zenoo.com/v1/cases/cas_xyz789" \
-H "Authorization: Bearer your-api-key"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.zenoo.com/v1/cases/cas_xyz789",
headers={"Authorization": "Bearer your-api-key"},
)
case = response.json()
print(f"Status: {case['status']}")
print(f"Risk Tier: {case['risk_tier']}")
print(f"Open Alerts: {case['open_alerts_count']}")
print(f"Checks: {case['checks_summary']['passed']}/{case['checks_summary']['total']} passed")
```
```javascript Node.js theme={null}
const caseRes = await fetch("https://api.zenoo.com/v1/cases/cas_xyz789", {
headers: { Authorization: "Bearer your-api-key" },
});
const caseData = await caseRes.json();
console.log(`Status: ${caseData.status}`);
console.log(`Risk Tier: ${caseData.risk_tier}`);
console.log(`Open Alerts: ${caseData.open_alerts_count}`);
console.log(
`Checks: ${caseData.checks_summary.passed}/${caseData.checks_summary.total} passed`
);
```
**Response:**
```json theme={null}
{
"token": "cas_xyz789",
"type": "Onboarding",
"status": "Closed",
"priority": "Medium",
"risk_tier": "Medium",
"risk_score": 45,
"open_alerts_count": 0,
"entity": {
"token": "ent_abc123",
"name": "Acme Holdings Ltd",
"type": "company"
},
"checks_summary": {
"total": 6,
"passed": 5,
"referred": 1,
"failed": 0,
"waived": 0,
"reused": 0
},
"due_date": "2026-03-02T10:00:00Z",
"completed_at": "2026-02-16T11:35:00Z",
"created_at": "2026-02-16T10:01:00Z"
}
```
Confirm there are no open alerts remaining on the case.
```bash curl theme={null}
curl -X GET "https://api.zenoo.com/v1/cases/cas_xyz789/alerts?status=Open" \
-H "Authorization: Bearer your-api-key"
```
```python Python theme={null}
response = requests.get(
"https://api.zenoo.com/v1/cases/cas_xyz789/alerts",
headers={"Authorization": "Bearer your-api-key"},
params={"status": "Open"},
)
open_alerts = response.json()
if open_alerts["meta"]["total"] > 0:
print(f"WARNING: {open_alerts['meta']['total']} alerts still open")
else:
print("All alerts resolved. Ready for report generation.")
```
```javascript Node.js theme={null}
const alertsRes = await fetch(
"https://api.zenoo.com/v1/cases/cas_xyz789/alerts?status=Open",
{ headers: { Authorization: "Bearer your-api-key" } }
);
const openAlerts = await alertsRes.json();
if (openAlerts.meta.total > 0) {
console.log(`WARNING: ${openAlerts.meta.total} alerts still open`);
} else {
console.log("All alerts resolved. Ready for report generation.");
}
```
The compliance report endpoint returns an error if any alerts remain open. Resolve all alerts before generating the report.
Request the full compliance report. The report aggregates all case data, check results, alert resolutions, risk assessments, and audit trail entries into a single structured document.
```bash curl theme={null}
curl -X GET "https://api.zenoo.com/v1/cases/cas_xyz789/compliance-report" \
-H "Authorization: Bearer your-api-key"
```
```python Python theme={null}
response = requests.get(
"https://api.zenoo.com/v1/cases/cas_xyz789/compliance-report",
headers={"Authorization": "Bearer your-api-key"},
)
report = response.json()
print(f"Verdict: {report['overall_verdict']}")
print(f"Risk Tier: {report['risk_assessment']['tier']}")
print(f"CDD Completed: {report['compliance_metadata']['cdd_completed']}")
```
```javascript Node.js theme={null}
const reportRes = await fetch(
"https://api.zenoo.com/v1/cases/cas_xyz789/compliance-report",
{ headers: { Authorization: "Bearer your-api-key" } }
);
const report = await reportRes.json();
console.log(`Verdict: ${report.overall_verdict}`);
console.log(`Risk Tier: ${report.risk_assessment.tier}`);
console.log(`CDD Completed: ${report.compliance_metadata.cdd_completed}`);
```
**Response:**
```json theme={null}
{
"case_reference": "AML-2026-0042",
"external_reference": "YOUR-REF-2026-0042",
"overall_verdict": "Pass",
"generated_at": "2026-02-16T12:00:00Z",
"entity": {
"name": "Acme Holdings Ltd",
"type": "company",
"registration_number": "12345678",
"jurisdiction": "GB",
"verification_status": "Verified"
},
"screening": {
"pep_status": "Cleared",
"sanctions_status": "No Hit",
"adverse_media_status": "No Hit",
"provider": "WorldCheck",
"completed_at": "2026-02-16T10:15:00Z"
},
"related_entities": [
{
"name": "Jane Smith",
"role": "Director",
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"verification_status": "Verified"
},
{
"name": "John Doe",
"role": "UBO",
"ownership_percentage": 75,
"pep_status": "Cleared",
"sanctions_status": "No Hit",
"verification_status": "Verified"
}
],
"risk_assessment": {
"overall_score": 45,
"tier": "Medium",
"customer_risk": { "score": 40, "tier": "Medium" },
"geographic_risk": { "score": 30, "tier": "Low" },
"product_risk": { "score": 55, "tier": "Medium" },
"channel_risk": { "score": 25, "tier": "Low" },
"factors": [
{
"dimension": "Customer",
"factor": "UBO identified as former PEP",
"severity": "Medium"
},
{
"dimension": "Product",
"factor": "Financial Services industry",
"severity": "Medium"
}
]
},
"checks_summary": [
{ "type": "Company Registry", "result": "Pass", "provider": "Kyckr" },
{ "type": "Company Screening", "result": "Pass", "provider": "WorldCheck" },
{ "type": "Director Screening (Jane Smith)", "result": "Pass", "provider": "WorldCheck" },
{ "type": "UBO Screening (John Doe)", "result": "Refer", "provider": "WorldCheck", "resolution": "Approved" },
{ "type": "Identity (John Doe)", "result": "Pass", "provider": "ZenooVerify" },
{ "type": "Directors Verification", "result": "Pass", "provider": "Kyckr" }
],
"alerts_summary": [
{
"category": "PEP Match",
"entity": "John Doe",
"resolution": "Approved",
"notes": "Former local councillor (2010-2014). Low risk position.",
"resolved_by": "analyst@example.com",
"resolved_at": "2026-02-16T11:30:00Z"
}
],
"compliance_metadata": {
"cdd_completed": true,
"cdd_completed_at": "2026-02-16T11:35:00Z",
"edd_required": false,
"reviewed_by": "analyst@example.com",
"case_type": "Onboarding",
"sla_met": true,
"audit_log_count": 18
}
}
```
The compliance report contains six sections. Use them to drive your onboarding decision.
### Section reference
| Section | Contains |
| - | - |
| `entity` | Primary entity details and verification status |
| `screening` | PEP, sanctions, and adverse media screening outcomes |
| `related_entities` | Directors, UBOs, shareholders with individual screening results |
| `risk_assessment` | 4-dimension risk score breakdown with contributing factors |
| `checks_summary` | Every check executed with result and provider |
| `alerts_summary` | Every alert generated with resolution, notes, and resolver |
| `compliance_metadata` | CDD/EDD status, SLA compliance, reviewer, audit log count |
### Decision matrix
| Verdict | Risk Tier | Recommended Action |
| - | - | - |
| `Pass` | Low | Auto-approve. Proceed with onboarding. |
| `Pass` | Medium | Approve with enhanced monitoring schedule. |
| `Refer` | Medium | Route to compliance analyst for manual review. |
| `Refer` | High | Route to senior compliance officer. Consider EDD. |
| `Fail` | Any | Reject. Escalate to compliance team. |
Store the full report JSON in your system for regulatory audit purposes. The `case_reference` and `audit_log_count` fields support traceability back to the immutable audit trail.
### Export format
The compliance report can also be requested as a PDF for filing:
```bash theme={null}
curl -X GET "https://api.zenoo.com/v1/cases/cas_xyz789/compliance-report?format=pdf" \
-H "Authorization: Bearer your-api-key" \
-o "compliance-report-AML-2026-0042.pdf"
```
## Monitoring schedule
After onboarding is complete, set up ongoing review based on the risk tier:
| Risk Tier | Review Frequency | Monitoring Level | Rationale |
| - | - | - | - |
| **High** | Every 6 months | Enhanced screening + approval | Highest regulatory scrutiny. Frequent changes in sanctions lists and PEP databases. |
| **Medium** | Every 12 months | Enhanced screening + review | Standard CDD cycle. Aligns with most regulatory frameworks. |
| **Low** | Every 24 months | Standard ongoing screening | Reduced frequency appropriate for low-risk customers with stable profiles. |
These intervals are recommendations. Your compliance program may require different schedules based on jurisdiction, customer type, or regulatory guidance.
Implement a review calendar that automatically schedules re-screening based on the risk tier returned in the compliance report. Use the `next_review_date` field from the compliance metadata to track when each customer is due.
Use case type `Perpetual` for ongoing monitoring cases and `Review` for periodic re-review cases. See [Case Lifecycle Guide](/guides/case-lifecycle) for details.
## Next steps
* [Case Lifecycle Guide](/guides/case-lifecycle). Understand case types and review schedules.
* [Risk Scoring Guide](/guides/risk-scoring). Deep dive into the 4-dimension risk model.
* [Audit Trail Guide](/guides/audit-trail). Query the full audit history for a case.
* [Webhook Events](/data-model/webhook-events). Automate post-onboarding monitoring workflows.
# Company Verification
Source: https://docs.zenoo.com/use-cases/kyb-verification
Verify a company end-to-end with registry checks, director screening, and UBO screening.
# Company Verification
Verify a company end-to-end with registry checks, director screening, and UBO screening. This guide covers submitting company data, retrieving results, and interpreting the compliance report.
## What Company Verification checks
When you submit a company for verification, Zenoo orchestrates four check types:
1. **Company Registry Verification**. Legal name, registration number, incorporation status, registered address, directors, and shareholders from official registries.
2. **Company Screening** (WorldCheck). PEP, sanctions, and adverse media screening on the company entity itself.
3. **Director Screening** (WorldCheck). Individual screening for each director you provide or that the registry returns.
4. **UBO Screening** (WorldCheck). Individual screening for each beneficial owner.
## Data tiers
You can submit three levels of data. More data produces better matching and richer risk scoring.
Providing Standard tier or above is recommended for production. Minimum tier works for testing but produces less accurate risk scoring.
The bare minimum to run a registry check and company screening.
```json theme={null}
{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB"
}
```
Adds directors, UBOs, and company details. This is the recommended minimum for production.
```json theme={null}
{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"company_type": "Private Limited Company (Ltd)",
"incorporation_date": "2015-03-15",
"industry": "Financial Services",
"address_line_1": "123 High Street",
"city": "London",
"postcode": "EC1A 1BB",
"directors": [
{
"first_name": "Jane",
"last_name": "Smith",
"date_of_birth": "1980-05-20",
"nationality": "GB",
"role": "Director"
}
],
"ubos": [
{
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1975-11-10",
"nationality": "GB",
"ownership_percentage": 75,
"role": "UBO"
}
]
}
```
Adds external reference for idempotency, financial data, and entity identifiers.
```json theme={null}
{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"company_type": "Private Limited Company (Ltd)",
"incorporation_date": "2015-03-15",
"industry": "Financial Services",
"address_line_1": "123 High Street",
"city": "London",
"postcode": "EC1A 1BB",
"external_reference": "YOUR-REF-2026-0042",
"annual_revenue": 5000000,
"employee_count": 45,
"sic_codes": ["64110", "64205"],
"status": "Active",
"entity_id": "ent-abc-123",
"directors": [
{
"first_name": "Jane",
"last_name": "Smith",
"date_of_birth": "1980-05-20",
"nationality": "GB",
"role": "Director",
"entity_id": "ent-dir-456"
}
],
"ubos": [
{
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1975-11-10",
"nationality": "GB",
"ownership_percentage": 75,
"role": "UBO",
"entity_id": "ent-ubo-789"
}
]
}
```
## Field reference
| Field | Type | Required | Description |
| - | - | - | - |
| `company_name` | string | Yes | Legal name of the company |
| `registration_number` | string | Yes | Official registration or incorporation number |
| `country` | string | Yes | ISO 3166-1 alpha-2 country code (e.g. `GB`, `US`, `DE`) |
| `company_type` | string | No | Legal entity type (e.g. `Private Limited Company (Ltd)`) |
| `incorporation_date` | string | No | Date of incorporation in `YYYY-MM-DD` format |
| `industry` | string | No | Industry classification or description |
| `address_line_1` | string | No | Registered office street address |
| `city` | string | No | Registered office city |
| `postcode` | string | No | Registered office postal code |
| `external_reference` | string | No | Your internal reference ID. Enables idempotency. |
| `annual_revenue` | number | No | Annual revenue in base currency units |
| `employee_count` | number | No | Number of employees |
| `sic_codes` | string\[] | No | Standard Industrial Classification codes |
| `status` | string | No | Company status (e.g. `Active`, `Dissolved`) |
| `entity_id` | string | No | Your internal entity identifier |
| `directors` | object\[] | No | Array of director objects (see below) |
| `ubos` | object\[] | No | Array of beneficial owner objects (see below) |
**Director and UBO fields:**
| Field | Type | Required | Description |
| - | - | - | - |
| `first_name` | string | Yes | First name |
| `last_name` | string | Yes | Last name |
| `date_of_birth` | string | No | Date of birth in `YYYY-MM-DD` format. Strongly recommended for screening accuracy. |
| `nationality` | string | No | ISO 3166-1 alpha-2 country code |
| `role` | string | No | Role in the company (e.g. `Director`, `UBO`, `Secretary`) |
| `ownership_percentage` | number | No | Beneficial ownership percentage (UBOs only) |
| `entity_id` | string | No | Your internal entity identifier |
### Sync mode (recommended for server-to-server)
Results are returned directly in the response body. Add the `X-SYNC-TIMEOUT` header to set the maximum wait time in milliseconds.
```bash theme={null}
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-H "X-SYNC-TIMEOUT: 60000" \
-d '{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"company_type": "Private Limited Company (Ltd)",
"incorporation_date": "2015-03-15",
"industry": "Financial Services",
"address_line_1": "123 High Street",
"city": "London",
"postcode": "EC1A 1BB",
"external_reference": "YOUR-REF-2026-0042",
"directors": [
{
"first_name": "Jane",
"last_name": "Smith",
"date_of_birth": "1980-05-20",
"nationality": "GB",
"role": "Director"
}
],
"ubos": [
{
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1975-11-10",
"nationality": "GB",
"ownership_percentage": 75,
"role": "UBO"
}
]
}'
```
### Async mode
If you prefer non-blocking execution, call the `/init` endpoint without the `X-SYNC-TIMEOUT` header.
```bash theme={null}
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/init" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-d '{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"external_reference": "YOUR-REF-2026-0042",
"directors": [
{
"first_name": "Jane",
"last_name": "Smith",
"date_of_birth": "1980-05-20",
"nationality": "GB",
"role": "Director"
}
],
"ubos": [
{
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1975-11-10",
"nationality": "GB",
"ownership_percentage": 75,
"role": "UBO"
}
]
}'
```
**Response:**
```json theme={null}
{
"tokens": {
"pull": "eyJhbGciOiJIUzI1NiJ9.pull-token...",
"start": "eyJhbGciOiJIUzI1NiJ9.start-token..."
}
}
```
Store the `pull` token immediately. It is the only way to retrieve results. If you lose it, you must reinitiate the verification.
### Sync mode
Results are returned directly in the response body. Skip to the next step.
### Async mode: poll for results
```bash theme={null}
curl -X GET \
"https://instance.prod.onboardapp.io/api/gateway/sharable-payload/{pull_token}"
```
| Status | Meaning |
| - | - |
| `200` with JSON body | Results are ready |
| `204` No Content | Still processing, retry after a delay |
| `404` | Invalid or expired token |
**Polling strategy:**
* First 2 minutes: poll every 10 seconds
* After 2 minutes: poll every 30 seconds
* Typical completion time: 30 seconds to 5 minutes
### Async mode: webhook notification
Configure a webhook endpoint to receive a `verification.completed` event when processing finishes. See [Webhooks Guide](/guides/webhooks) for setup instructions.
You can use both strategies together: webhooks for real-time notification, polling as a fallback.
A complete Company Verification response contains six sections: company details, screening results, beneficial owners, directors, risk assessment, and checks summary.
```json {5-6} theme={null}
{
"case_reference": "AML-2026-0042",
"external_reference": "YOUR-REF-2026-0042",
"processing_status": "complete",
"overall_verdict": "Refer",
"risk_tier": "Medium",
"completed_at": "2026-01-15T14:32:00Z",
"company": {
"legal_name": "Acme Holdings Limited",
"registration_number": "12345678",
"jurisdiction": "GB",
"company_status": "Active",
"company_type": "Private Limited Company",
"incorporation_date": "2015-03-15",
"registered_address": "123 High Street, London, EC1A 1BB",
"verification_status": "Verified",
"verified_at": "2026-01-15T14:30:15Z"
},
"screening": {
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "Hit",
"adverse_media_count": 2,
"screening_provider": "WorldCheck",
"screening_completed_at": "2026-01-15T14:30:20Z"
},
"beneficial_owners": [
{
"name": "John Doe",
"date_of_birth": "1975-11-10",
"nationality": "GB",
"ownership_percentage": 75,
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "No Hit",
"identity_verified": false
}
],
"directors": [
{
"name": "Jane Smith",
"date_of_birth": "1980-05-20",
"role": "Director",
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "No Hit"
}
],
"risk_assessment": {
"overall_score": 45,
"tier": "Medium",
"factors": [
{
"factor": "Adverse Media",
"severity": "Medium",
"detail": "2 adverse media articles found for company"
}
]
},
"checks_summary": {
"total": 5,
"passed": 4,
"referred": 1,
"failed": 0,
"checks": [
{
"type": "Company Registration",
"status": "Pass",
"provider": "Registry"
},
{
"type": "Company Screening",
"status": "Refer",
"provider": "WorldCheck",
"matches": 2
},
{
"type": "Director Screening (Jane Smith)",
"status": "Pass",
"provider": "WorldCheck"
},
{
"type": "UBO Screening (John Doe)",
"status": "Pass",
"provider": "WorldCheck"
},
{
"type": "Directors Verification",
"status": "Pass",
"provider": "Registry"
}
]
},
"compliance_metadata": {
"cdd_completed": true,
"cdd_completed_at": "2026-01-15T14:32:00Z",
"edd_required": false,
"reviewed_by": "system"
}
}
```
Key fields to check:
* `overall_verdict`. The top-level recommendation: `Pass`, `Refer`, or `Fail`.
* `risk_tier`. Risk classification: `Low`, `Medium`, or `High`.
* `screening.*_status`. Whether any screening category returned a match.
* `checks_summary.checks[]`. Status of each individual check.
Store the `case_reference` in your database. You will need it for support queries, audit trails, and linking future updates.
Use the `overall_verdict` and `risk_tier` together to drive your onboarding workflow.
| Verdict | Risk Tier | Recommended Action |
| - | - | - |
| `Pass` | Low | Auto-approve. Proceed with onboarding. |
| `Pass` | Medium | Auto-approve with enhanced monitoring. |
| `Refer` | Medium | Route to compliance analyst for manual review. |
| `Refer` | High | Route to senior compliance officer. Consider EDD. |
| `Fail` | Any | Reject. Escalate to compliance team. |
If `sanctions_status` is `"Hit"` on any entity (company, director, or UBO), halt onboarding immediately and escalate. Processing sanctioned entities may violate legal obligations in your jurisdiction.
## How checks are orchestrated
When you submit a company for verification, the check framework automatically creates, executes, and tracks every verification requirement. Here is the orchestration flow:
```mermaid theme={null}
flowchart TD
A[Entity added to case] --> B[Read Verification Type config]
B --> C{Execution method?}
C -->|API / Manual| D[Create Required Check]
C -->|Document / FreeText / Dropdown| E[Create Document Requirement]
C -->|Hybrid| F[Create Both with cross-links]
D --> G{Auto-execute?}
G -->|Yes| H[Execute via API adapter]
G -->|No| I[Status: Pending]
H --> J[Process result]
J -->|Pass| K[Check Complete]
J -->|Refer / Fail| L[Generate Alert]
```
For Company Verification, all four check types (registry, company screening, director screening, UBO screening) are API-based and auto-execute immediately after case creation. You do not need to trigger them manually.
Checks that return `Refer` or `Fail` automatically generate alerts in the case management system. Alerts for sanctions hits are always marked Critical priority.
## Verdict mapping
The full Company Verification verdict mapping combines `overall_verdict` and `risk_tier` to determine the appropriate action across all scenarios:
| Verdict | Risk Tier | Recommended Action |
| - | - | - |
| Pass | Low | Auto-approve. Proceed with onboarding. |
| Pass | Medium | Auto-approve with enhanced monitoring. Schedule periodic re-screening. |
| Refer | Low | Quick manual review. Likely a minor data issue. |
| Refer | Medium | Manual review required. Assign to a compliance analyst. |
| Refer | High | Senior compliance review. Consider enhanced due diligence (EDD). |
| Fail | Low | Investigate the specific failure. May be a data quality issue. |
| Fail | Medium | Reject or request corrected information. |
| Fail | High | Reject. Escalate to compliance team for review. |
### Auto-approve criteria
You can safely auto-approve a Company Verification when ALL of the following are true:
* `overall_verdict` is `"Pass"`.
* `risk_tier` is `"Low"`.
* `sanctions_status` is `"No Hit"` on ALL entities (company, directors, UBOs).
* `pep_status` is `"No Hit"` on ALL entities.
* `adverse_media_status` is `"No Hit"`.
```javascript decision-handler.js theme={null}
function canAutoApprove(result) {
if (result.overall_verdict !== "Pass") return false;
if (result.risk_tier !== "Low") return false;
// Check all screening statuses
const screening = result.screening || {};
if (screening.pep_status !== "No Hit") return false;
if (screening.sanctions_status !== "No Hit") return false;
if (screening.adverse_media_status !== "No Hit") return false;
// Check directors and UBOs
const entities = [
...(result.directors || []),
...(result.beneficial_owners || [])
];
for (const entity of entities) {
if (entity.pep_status !== "No Hit") return false;
if (entity.sanctions_status !== "No Hit") return false;
}
return true;
}
```
Any other combination should go to manual review. Do not attempt to auto-approve verifications that do not meet every criterion above.
## Idempotency
Always include `external_reference` in production requests to prevent duplicate verifications. If you submit the same reference twice, Zenoo returns the existing case tokens without creating a duplicate.
```json theme={null}
{
"company_name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"external_reference": "YOUR-REF-2026-0042"
}
```
## Country codes
Use ISO 3166-1 alpha-2 codes for all country fields. Common codes:
| Country | Code |
| - | - |
| United Kingdom | `GB` (not `UK`) |
| United States | `US` |
| Ireland | `IE` |
| Germany | `DE` |
| France | `FR` |
| Netherlands | `NL` |
| Switzerland | `CH` |
The `country` field determines which registry Zenoo queries and which screening jurisdiction applies.
Using `UK` instead of `GB` will return an error. Always use the ISO 3166-1 alpha-2 standard code `GB` for United Kingdom.
## Next steps
* [Person Verification](/use-cases/kyc-verification). Verify directors and UBOs individually with document and biometric checks.
* [Screening](/use-cases/screening). Run standalone screening for ongoing monitoring.
* [Sync vs Async](/guides/sync-vs-async). Choose the right execution model for your integration.
# Person Verification
Source: https://docs.zenoo.com/use-cases/kyc-verification
Verify an individual end-to-end with document capture, biometric liveness, face matching, database checks, and screening.
# Person Verification
Verify an individual end-to-end with document capture, biometric liveness, face matching, database checks, and screening. This guide covers initiating a journey, redirecting the user, and interpreting structured results.
## What Person Verification checks
A Person Verification journey orchestrates up to six check types:
1. **Identity Document Capture**. Passport, national ID, or driving licence scanned with OCR.
2. **Biometric Liveness**. Selfie with liveness detection to confirm the user is physically present.
3. **Face Match**. Compares the selfie against the document photo.
4. **Document Authenticity**. Tamper detection and fraud analysis on the captured document.
5. **Database Verification**. Name, address, and date of birth validated against official databases.
6. **PEP/Sanctions Screening** (WorldCheck). Screening on the individual across PEP, sanctions, adverse media, and watchlist databases.
Checks 1 through 4 require user interaction and use the async journey flow. Checks 5 and 6 can run server-side without user interaction. See [Server-side Person Verification](#server-side-person-verification-no-user-interaction) below.
## Data tiers
### Tier 1: Minimum
The bare minimum to initiate a Person Verification journey.
```json theme={null}
{
"first_name": "Jane",
"last_name": "Smith",
"country": "GB"
}
```
### Tier 2: Standard
Adds date of birth, contact details, and address. This is the recommended minimum for production.
```json theme={null}
{
"first_name": "Jane",
"last_name": "Smith",
"country": "GB",
"date_of_birth": "1990-06-15",
"email": "jane.smith@example.com",
"phone": "+447700900123",
"address": "42 Baker Street",
"city": "London",
"postal_code": "NW1 6XE"
}
```
### Tier 3: Full
Adds nationality, document type preference, and external reference for idempotency.
```json theme={null}
{
"first_name": "Jane",
"last_name": "Smith",
"country": "GB",
"date_of_birth": "1990-06-15",
"email": "jane.smith@example.com",
"phone": "+447700900123",
"address": "42 Baker Street",
"city": "London",
"postal_code": "NW1 6XE",
"nationality": "GB",
"document_type": "passport",
"external_reference": "YOUR-REF-2026-0123"
}
```
## Flow overview
```
Your App Zenoo User
| | |
|---- 1. POST /kyc/init -->| |
|<--- Tokens (pull+start) -| |
| | |
|---- 2. Redirect user -------------------------------->|
| |<-- 3. Doc + selfie ------|
| |--- 4. Process checks --->|
| | |
|<--- 5. Webhook ----------| |
|---- 6. GET /results ---->| |
|<--- Full results --------| |
```
Person Verification uses async Model 2 because the user must interact with the verification UI. Post to the `/kyc/init` endpoint.
```bash theme={null}
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/kyc/init" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-d '{
"first_name": "Jane",
"last_name": "Smith",
"date_of_birth": "1990-06-15",
"email": "jane.smith@example.com",
"phone": "+447700900123",
"country": "GB",
"address": "42 Baker Street",
"city": "London",
"postal_code": "NW1 6XE",
"external_reference": "YOUR-REF-2026-0123"
}'
```
**Response:**
```json theme={null}
{
"tokens": {
"pull": "eyJhbGciOiJIUzI1NiJ9.pull-token...",
"start": "eyJhbGciOiJIUzI1NiJ9.start-token..."
}
}
```
Store both tokens immediately:
* **`pull`**. Retrieves verification results.
* **`start`**. Constructs the verification URL for the user.
Build the URL from the `start` token and your project hash:
```
https://instance.prod.onboardapp.io/{project_hash}/?t={start_token}
```
### Web integration
```html theme={null}
Verify Your Identity
```
Or redirect server-side:
```javascript server.js theme={null}
res.redirect(
302,
`https://instance.prod.onboardapp.io/${projectHash}/?t=${startToken}`
);
```
### Mobile integration
Open the URL in a WebView or the system browser. The verification UI is responsive and works on all screen sizes.
```swift iOS theme={null}
let url = URL(string: "https://instance.prod.onboardapp.io/\(projectHash)/?t=\(startToken)")!
UIApplication.shared.open(url)
```
```kotlin Android theme={null}
val intent = Intent(Intent.ACTION_VIEW,
Uri.parse("https://instance.prod.onboardapp.io/$projectHash/?t=$startToken"))
startActivity(intent)
```
### URL expiry
Verification URLs expire after **24 hours**. If the user does not complete the flow within this window, you receive a `journey.expired` webhook and must initiate a new journey.
The user goes through an interactive flow:
1. **Document selection**. Choose a document type: passport, national ID, or driving licence.
2. **Document capture**. Photograph the front (and back if applicable) of the document.
3. **Selfie capture**. Take a selfie for biometric matching.
4. **Liveness check**. Follow on-screen prompts to prove physical presence.
5. **Submission**. Data is sent to Zenoo for processing.
Server-side processing typically completes 10 to 30 seconds after the user submits.
### Option A: Webhook (recommended)
Configure a webhook endpoint to receive `verification.completed` events.
```json theme={null}
{
"event_type": "verification.completed",
"timestamp": "2026-01-15T14:35:00Z",
"journey_id": "jrn-abc123",
"callback_reference": "YOUR-REF-2026-0123",
"status": "completed",
"data": { "...": "..." }
}
```
See [Webhooks Guide](/guides/webhooks) for setup.
### Option B: Poll for results
```bash theme={null}
curl -X GET \
"https://instance.prod.onboardapp.io/api/gateway/sharable-payload/{pull_token}"
```
| Status | Meaning |
| - | - |
| `200` with JSON body | Results are ready |
| `204` No Content | Still processing, retry after a delay |
| `404` | Invalid or expired token |
### Option C: Both
Use webhooks for real-time notification and polling as a fallback for missed webhooks. This is the most resilient pattern.
A complete Person Verification response includes identity verification, document details, biometric results, screening, phone verification, email verification, address verification, and compliance metadata.
```json {8,12} theme={null}
{
"case_reference": "AML-2026-0123",
"external_reference": "YOUR-REF-2026-0123",
"processing_status": "complete",
"completed_at": "2026-01-15T14:35:30Z",
"identity": {
"verified": true,
"decision": "Pass",
"score": 95,
"authentication_id": "auth-xyz789"
},
"document": {
"verified": true,
"document_type": "Passport",
"document_country": "GB",
"document_number": "123456789",
"validation_result": "PASSED",
"tamper_result": "Pass",
"extracted_data": {
"full_name": "JANE ELIZABETH SMITH",
"date_of_birth": "1990-06-15",
"expiration_date": "2030-12-01",
"gender": "F",
"nationality": "GBR",
"mrz_line_1": "P
Only auto-approve the `Pass / Pass / Pass / No Hit` combination. Any other outcome should trigger manual review or re-submission.
A document `ATTENTION` result means the document requires human review, not necessarily rejection. Check `document.validation_result` before making a pass/fail decision.
### Check dimension statuses
**Identity statuses:**
| Status | Meaning |
| - | - |
| Pass | Name, address, and DOB confirmed against database records |
| Fail | Could not verify identity against databases |
**Document statuses:**
| Status | Meaning |
| - | - |
| Pass | Document is authentic, not expired, and data matches |
| Attention | Possible issue detected (blurry image, minor inconsistency). Manual review needed. |
| Fail | Document rejected (expired, tampered, unrecognized) |
**Biometric statuses:**
| Status | Meaning |
| - | - |
| Pass | Liveness confirmed and face matches document photo |
| Fail | Liveness check failed or face does not match |
**Screening statuses:**
| Status | Meaning |
| - | - |
| No Hit | No matches in PEP, sanctions, adverse media, or watchlist databases |
| Hit | One or more potential matches found. Review required. |
## Handling edge cases
### journey.abandoned
The user closed the browser or navigated away before completing verification.
```json theme={null}
{
"event_type": "journey.abandoned",
"journey_id": "jrn-abc123",
"callback_reference": "YOUR-REF-2026-0123"
}
```
The original verification URL remains valid until it expires. Send the user a reminder with the same link. If the URL has expired, initiate a new journey.
### journey.expired
The 24-hour URL expired before the user completed the flow.
```json theme={null}
{
"event_type": "journey.expired",
"journey_id": "jrn-abc123",
"callback_reference": "YOUR-REF-2026-0123"
}
```
Initiate a new journey and send the user a fresh link.
### Document issues
If the document fails authenticity checks, `document.validation_result` will be `ATTENTION` or `FAILED`. Common causes:
* Blurry or glare-obscured image
* Expired document
* Tamper indicators detected
* Document type not supported for the selected country
Check `document.validation_result` before making a pass/fail decision. An `ATTENTION` result means the document requires human review, not necessarily rejection.
## Server-side Person Verification (no user interaction)
For database lookups, phone verification, and screening that do not require document capture, use the sync endpoint directly.
```bash theme={null}
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/kyc/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{
"first_name": "Jane",
"last_name": "Smith",
"date_of_birth": "1990-06-15",
"phone": "+447700900123",
"email": "jane.smith@example.com",
"country": "GB",
"address": "42 Baker Street",
"city": "London",
"postal_code": "NW1 6XE"
}'
```
This runs database verification, phone checks, email checks, address checks, and screening synchronously. Results are returned in the response body. No document capture or biometric checks are performed.
Use server-side Person Verification for:
* Batch processing existing customer data
* Pre-screening before initiating a full journey
* Ongoing monitoring re-checks on known individuals
* Flows where you already have verified identity documents from another source
## Next steps
Run standalone screening checks
Set up real-time notifications
Understand when to use each execution model
Handle failures and retries
# End-to-End Onboarding
Source: https://docs.zenoo.com/use-cases/onboarding
Walk through a full company onboarding lifecycle via the API: create an entity, open a case, run checks, review alerts, and close the case.
# End-to-End Onboarding
This guide walks you through a complete company onboarding lifecycle using the Zenoo CLM API. By the end you will have created an entity, opened an onboarding case, executed compliance checks, reviewed generated alerts, and closed the case.
## Prerequisites
* A valid API key (see [Authentication](/getting-started/authentication))
* Base URL: `https://api.zenoo.com/v1`
## What happens during onboarding
When you create an onboarding case, Zenoo automatically:
1. Creates verification requirements based on entity type and role
2. Executes API-based checks (screening, registry, identity)
3. Generates alerts for any hits (PEP, sanctions, adverse media)
4. Calculates a 4-dimension risk score
Register the company you want to onboard. The response includes the entity token used in all subsequent calls.
```bash curl theme={null}
curl -X POST https://api.zenoo.com/v1/entities \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"type": "company",
"name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"incorporation_date": "2015-03-15",
"industry": "Financial Services",
"directors": [
{
"first_name": "Jane",
"last_name": "Smith",
"date_of_birth": "1980-05-20",
"nationality": "GB",
"role": "Director"
}
],
"ubos": [
{
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1975-11-10",
"nationality": "GB",
"ownership_percentage": 75,
"role": "UBO"
}
]
}'
```
```python Python theme={null}
import requests
response = requests.post(
"https://api.zenoo.com/v1/entities",
headers={
"Authorization": "Bearer your-api-key",
"Content-Type": "application/json",
},
json={
"type": "company",
"name": "Acme Holdings Ltd",
"registration_number": "12345678",
"country": "GB",
"incorporation_date": "2015-03-15",
"industry": "Financial Services",
"directors": [
{
"first_name": "Jane",
"last_name": "Smith",
"date_of_birth": "1980-05-20",
"nationality": "GB",
"role": "Director",
}
],
"ubos": [
{
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1975-11-10",
"nationality": "GB",
"ownership_percentage": 75,
"role": "UBO",
}
],
},
)
entity = response.json()
print(entity["token"]) # ent_abc123
```
```javascript Node.js theme={null}
const response = await fetch("https://api.zenoo.com/v1/entities", {
method: "POST",
headers: {
Authorization: "Bearer your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
type: "company",
name: "Acme Holdings Ltd",
registration_number: "12345678",
country: "GB",
incorporation_date: "2015-03-15",
industry: "Financial Services",
directors: [
{
first_name: "Jane",
last_name: "Smith",
date_of_birth: "1980-05-20",
nationality: "GB",
role: "Director",
},
],
ubos: [
{
first_name: "John",
last_name: "Doe",
date_of_birth: "1975-11-10",
nationality: "GB",
ownership_percentage: 75,
role: "UBO",
},
],
}),
});
const entity = await response.json();
console.log(entity.token); // ent_abc123
```
**Response:**
```json theme={null}
{
"token": "ent_abc123",
"type": "company",
"name": "Acme Holdings Ltd",
"status": "active",
"created_at": "2026-02-16T10:00:00Z",
"related_entities": [
{ "token": "ent_dir456", "name": "Jane Smith", "role": "Director" },
{ "token": "ent_ubo789", "name": "John Doe", "role": "UBO" }
]
}
```
Open an onboarding case linked to the entity. Zenoo automatically creates verification requirements and begins executing API-based checks.
```bash curl theme={null}
curl -X POST https://api.zenoo.com/v1/cases \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"type": "Onboarding",
"entity_token": "ent_abc123",
"priority": "Medium",
"external_reference": "YOUR-REF-2026-0042"
}'
```
```python Python theme={null}
response = requests.post(
"https://api.zenoo.com/v1/cases",
headers={
"Authorization": "Bearer your-api-key",
"Content-Type": "application/json",
},
json={
"type": "Onboarding",
"entity_token": "ent_abc123",
"priority": "Medium",
"external_reference": "YOUR-REF-2026-0042",
},
)
case = response.json()
print(case["token"]) # cas_xyz789
```
```javascript Node.js theme={null}
const caseRes = await fetch("https://api.zenoo.com/v1/cases", {
method: "POST",
headers: {
Authorization: "Bearer your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
type: "Onboarding",
entity_token: "ent_abc123",
priority: "Medium",
external_reference: "YOUR-REF-2026-0042",
}),
});
const caseData = await caseRes.json();
console.log(caseData.token); // cas_xyz789
```
**Response:**
```json theme={null}
{
"token": "cas_xyz789",
"type": "Onboarding",
"status": "New",
"priority": "Medium",
"entity_token": "ent_abc123",
"due_date": "2026-03-02T10:00:00Z",
"created_at": "2026-02-16T10:01:00Z"
}
```
The `due_date` is automatically calculated from SLA configuration based on case type and priority. An Onboarding/Medium case defaults to 14 calendar days.
Zenoo auto-creates checks based on entity type and role. List them to track progress.
```bash curl theme={null}
curl -X GET "https://api.zenoo.com/v1/cases/cas_xyz789/checks" \
-H "Authorization: Bearer your-api-key"
```
```python Python theme={null}
response = requests.get(
"https://api.zenoo.com/v1/cases/cas_xyz789/checks",
headers={"Authorization": "Bearer your-api-key"},
)
checks = response.json()
for check in checks["data"]:
print(f"{check['type']} - {check['status']}")
```
```javascript Node.js theme={null}
const checksRes = await fetch(
"https://api.zenoo.com/v1/cases/cas_xyz789/checks",
{ headers: { Authorization: "Bearer your-api-key" } }
);
const checks = await checksRes.json();
checks.data.forEach((c) => console.log(`${c.type} - ${c.status}`));
```
**Response:**
```json theme={null}
{
"data": [
{ "token": "chk_001", "type": "PEP Screening", "category": "Screening", "entity": "Acme Holdings Ltd", "status": "In Progress" },
{ "token": "chk_002", "type": "Sanctions Screening", "category": "Screening", "entity": "Acme Holdings Ltd", "status": "In Progress" },
{ "token": "chk_003", "type": "Company Registry", "category": "Company", "entity": "Acme Holdings Ltd", "status": "Queued" },
{ "token": "chk_004", "type": "PEP Screening", "category": "Screening", "entity": "Jane Smith", "status": "Queued" },
{ "token": "chk_005", "type": "PEP Screening", "category": "Screening", "entity": "John Doe", "status": "Queued" },
{ "token": "chk_006", "type": "Identity Verification", "category": "Identity", "entity": "John Doe", "status": "Pending" }
],
"meta": { "total": 6 }
}
```
Checks execute asynchronously. Use webhooks (recommended) or poll the case status.
**Option A: Webhook**
Configure a webhook endpoint to receive `check.completed` and `check.failed` events. See [Webhook Events](/data-model/webhook-events).
**Option B: Poll**
```bash curl theme={null}
curl -X GET "https://api.zenoo.com/v1/cases/cas_xyz789" \
-H "Authorization: Bearer your-api-key"
```
```python Python theme={null}
import time
while True:
response = requests.get(
"https://api.zenoo.com/v1/cases/cas_xyz789",
headers={"Authorization": "Bearer your-api-key"},
)
case = response.json()
if case["checks_summary"]["pending"] == 0:
break
time.sleep(10)
```
```javascript Node.js theme={null}
async function waitForChecks(caseToken) {
while (true) {
const res = await fetch(
`https://api.zenoo.com/v1/cases/${caseToken}`,
{ headers: { Authorization: "Bearer your-api-key" } }
);
const data = await res.json();
if (data.checks_summary.pending === 0) return data;
await new Promise((r) => setTimeout(r, 10000));
}
}
```
After checks complete, any screening hits generate alerts that require analyst review.
```bash curl theme={null}
curl -X GET "https://api.zenoo.com/v1/cases/cas_xyz789/alerts" \
-H "Authorization: Bearer your-api-key"
```
```python Python theme={null}
response = requests.get(
"https://api.zenoo.com/v1/cases/cas_xyz789/alerts",
headers={"Authorization": "Bearer your-api-key"},
)
alerts = response.json()
for alert in alerts["data"]:
print(f"{alert['category']} - {alert['status']} (score: {alert['priority_score']})")
```
```javascript Node.js theme={null}
const alertsRes = await fetch(
"https://api.zenoo.com/v1/cases/cas_xyz789/alerts",
{ headers: { Authorization: "Bearer your-api-key" } }
);
const alerts = await alertsRes.json();
alerts.data.forEach((a) =>
console.log(`${a.category} - ${a.status} (score: ${a.priority_score})`)
);
```
**Response:**
```json theme={null}
{
"data": [
{
"token": "alt_alert01",
"type": "Screening",
"category": "PEP Match",
"title": "PEP Match: John Doe",
"status": "Open",
"priority": "High",
"priority_score": 78,
"match_score": 92,
"entity_name": "John Doe",
"sla_due_date": "2026-02-18T10:00:00Z"
}
],
"meta": { "total": 1 }
}
```
Review the alert details and resolve it with an action and justification.
```bash curl theme={null}
curl -X POST https://api.zenoo.com/v1/alerts/alt_alert01/resolve \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"action": "Approve",
"notes": "PEP match confirmed as former local councillor (2010-2014). Low risk position, no current exposure. EDD not required."
}'
```
```python Python theme={null}
response = requests.post(
"https://api.zenoo.com/v1/alerts/alt_alert01/resolve",
headers={
"Authorization": "Bearer your-api-key",
"Content-Type": "application/json",
},
json={
"action": "Approve",
"notes": "PEP match confirmed as former local councillor (2010-2014). Low risk position, no current exposure. EDD not required.",
},
)
print(response.json()["status"]) # Resolved
```
```javascript Node.js theme={null}
const resolveRes = await fetch(
"https://api.zenoo.com/v1/alerts/alt_alert01/resolve",
{
method: "POST",
headers: {
Authorization: "Bearer your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
action: "Approve",
notes:
"PEP match confirmed as former local councillor (2010-2014). Low risk position, no current exposure. EDD not required.",
}),
}
);
const resolved = await resolveRes.json();
console.log(resolved.status); // Resolved
```
**Response:**
```json theme={null}
{
"token": "alt_alert01",
"status": "Resolved",
"resolution_action": "Approve",
"resolved_at": "2026-02-16T11:30:00Z"
}
```
A case cannot be closed while it has open alerts. Resolve or escalate every alert before closing.
Once all alerts are resolved, close the case with a summary.
```bash curl theme={null}
curl -X POST https://api.zenoo.com/v1/cases/cas_xyz789/close \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"resolution_notes": "Company verified. Registry check passed. One PEP match on UBO (former local councillor) reviewed and approved. Risk tier: Medium."
}'
```
```python Python theme={null}
response = requests.post(
"https://api.zenoo.com/v1/cases/cas_xyz789/close",
headers={
"Authorization": "Bearer your-api-key",
"Content-Type": "application/json",
},
json={
"resolution_notes": "Company verified. Registry check passed. One PEP match on UBO (former local councillor) reviewed and approved. Risk tier: Medium.",
},
)
print(response.json()["status"]) # Closed
```
```javascript Node.js theme={null}
const closeRes = await fetch(
"https://api.zenoo.com/v1/cases/cas_xyz789/close",
{
method: "POST",
headers: {
Authorization: "Bearer your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
resolution_notes:
"Company verified. Registry check passed. One PEP match on UBO (former local councillor) reviewed and approved. Risk tier: Medium.",
}),
}
);
const closed = await closeRes.json();
console.log(closed.status); // Closed
```
**Response:**
```json theme={null}
{
"token": "cas_xyz789",
"status": "Closed",
"risk_tier": "Medium",
"risk_score": 45,
"completed_at": "2026-02-16T11:35:00Z",
"checks_summary": { "total": 6, "passed": 5, "referred": 1, "failed": 0 }
}
```
## Next steps
* [Alert Review](/use-cases/alert-review). Triage and resolve alerts in bulk via the API.
* [Case Lifecycle Guide](/guides/case-lifecycle). Understand all status transitions and SLA rules.
* [Check Orchestration Guide](/guides/check-orchestration). Learn how checks are auto-created and executed.
* [Webhook Events](/data-model/webhook-events). Set up real-time notifications for case and check events.
# Screening
Source: https://docs.zenoo.com/use-cases/screening
Screen individuals and companies against PEP, sanctions, adverse media, and watchlist databases.
# Screening
Screen individuals and companies against PEP, sanctions, adverse media, and watchlist databases. Screening runs synchronously via WorldCheck and returns structured match results.
Screening runs automatically as part of Company and Person Verification flows. Use the standalone screening endpoint for ongoing monitoring, pre-verification checks, or batch processing.
## Screening categories
| Category | What it checks | Example matches |
| - | - | - |
| **PEP** | Politically Exposed Persons | Heads of state, senior government officials, their family and associates |
| **Sanctions** | Global sanctions lists | OFAC SDN, EU Consolidated, UN Security Council, UK HMT |
| **Adverse Media** | Negative news coverage | Financial crime, fraud, terrorism financing, money laundering |
| **Watchlist** | Law enforcement databases | Interpol, FBI, national fugitive lists |
## Screen a person
Always include `date_of_birth` when screening individuals. It is the single most effective field for reducing false positives. Without it, common names like "John Smith" can return hundreds of matches. With it, the result set typically shrinks by 90% or more.
```bash theme={null}
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/screening/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{
"name": "John Smith",
"date_of_birth": "1975-03-20",
"country": "GB",
"entity_type": "person",
"categories": ["pep", "sanctions", "adverse_media", "watchlist"]
}'
```
## Screen a company
```bash theme={null}
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/screening/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{
"name": "Acme Holdings Ltd",
"country": "GB",
"entity_type": "company",
"categories": ["sanctions", "adverse_media"]
}'
```
## Clean result (no matches)
When an entity has no matches across any category:
```json {3-6} theme={null}
{
"screening_status": "complete",
"pep_status": "No Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "No Hit",
"watchlist_status": "No Hit",
"screening_provider": "WorldCheck",
"screening_completed_at": "2026-01-15T14:30:00Z",
"total_matches": 0,
"matches": []
}
```
An empty `matches` array with all status fields showing `"No Hit"` means the entity does not appear in any screened database.
## Result with matches
When screening returns hits, each match includes a score, category, and supporting data:
```json {3,5,12} theme={null}
{
"screening_status": "complete",
"pep_status": "Hit",
"sanctions_status": "No Hit",
"adverse_media_status": "Hit",
"watchlist_status": "No Hit",
"screening_provider": "WorldCheck",
"screening_completed_at": "2026-01-15T14:30:00Z",
"total_matches": 2,
"top_match_score": 95.0,
"matches": [
{
"matchScore": 95.0,
"matchStrength": "EXACT",
"primaryName": "John Andrew Smith",
"category": "pep",
"categories": ["pep-class-2"],
"types": ["pep-class-2"],
"gender": "MALE",
"nationality": "British",
"dateOfBirth": "1975-03-20",
"countries": [
"United Kingdom (Nationality)",
"United Kingdom (Location)"
],
"sources": ["UK Parliament", "Companies House"],
"enteredDate": "2018-04-12",
"lastUpdated": "2025-11-03",
"positions": [
{
"title": "Member of Parliament",
"country": "United Kingdom",
"from": "2019-12-12",
"to": "2024-07-04"
}
],
"associates": [
{
"name": "Mary Smith",
"association": "Spouse",
"type": "family"
}
]
},
{
"matchScore": 78.5,
"matchStrength": "STRONG",
"primaryName": "John Smith",
"category": "adverse_media",
"categories": ["adverse-media-financial-crime"],
"types": ["adverse-media"],
"gender": "MALE",
"nationality": "British",
"dateOfBirth": "1975-03-20",
"countries": ["United Kingdom (Nationality)"],
"sources": ["Financial Times", "BBC News"],
"enteredDate": "2025-06-15",
"lastUpdated": "2025-09-22",
"media": [
{
"title": "Investigation into financial irregularities at Acme Holdings",
"url": "https://example.com/article-123",
"snippet": "Authorities are investigating potential financial irregularities...",
"date": "2025-06-15"
}
]
}
]
}
```
## Match score interpretation
Match scores range from 0 to 100 and indicate the confidence that a screened entity matches a database record.
| Score | Confidence | Recommended action |
| - | - | - |
| 90-100 | Very High | Strong match. Investigate immediately. |
| 80-89 | High | Likely match. Manual review required. |
| 70-79 | Medium | Possible match. Review recommended, especially with other risk factors. |
| Below 70 | Low | Weak match. Likely a false positive, but review if combined with high-risk indicators. |
Do not auto-dismiss matches in the 70-79 range. WorldCheck scoring is conservative, and a 75% match on a sanctioned entity still requires investigation.
## PEP classifications
WorldCheck classifies Politically Exposed Persons into four tiers based on their level of public office.
| Class | Description | Examples |
| - | - | - |
| `pep-class-1` | Heads of state and senior national government | Presidents, prime ministers, cabinet ministers, supreme court justices |
| `pep-class-2` | Regional and legislative officials | Members of parliament, senators, governors, mayors of major cities |
| `pep-class-3` | Other public officials | Senior military officers, central bank board members, ambassadors, judiciary |
| `pep-class-4` | Associates and family members | Spouses, children, parents, known business partners of PEP Class 1-3 |
PEP Class 1 matches must always be escalated to a senior compliance officer regardless of match score. These individuals carry the highest regulatory scrutiny under FATF recommendations.
## Sanctions list types
| List | Source | Jurisdiction |
| - | - | - |
| `sanction-sdn` | OFAC Specially Designated Nationals | United States |
| `sanction-eu` | EU Consolidated Sanctions List | European Union |
| `sanction-un` | UN Security Council Consolidated List | International |
| `sanction-hmt` | HM Treasury Consolidated List | United Kingdom |
Any sanctions match, regardless of score, must halt onboarding immediately. Processing transactions for sanctioned entities carries severe penalties including criminal prosecution, asset freezes, and regulatory enforcement action.
## Match management decision matrix
| Category | Score | Action |
| - | - | - |
| Sanctions | Any score | Immediate escalation. Do not proceed with onboarding. |
| Watchlist | Any score | Immediate escalation. Do not proceed with onboarding. |
| PEP Class 1-2 | 80+ | Enhanced due diligence required. Senior compliance review. |
| PEP Class 1-2 | 70-79 | Standard review. Verify match relevance. |
| PEP Class 3-4 | 80+ | Standard review with ongoing monitoring. |
| PEP Class 3-4 | 70-79 | Log and monitor. Review if combined with other risk factors. |
| Adverse Media | 85+ | Manual review required. Assess severity and recency of media. |
| Adverse Media | 70-84 | Review if combined with other risk factors (high-risk jurisdiction, complex ownership). |
| Adverse Media | Below 70 | Log for audit trail. Likely false positive. |
## Sanctions halt logic
If `sanctions_status` is `"Hit"` on any entity, your system must immediately halt all processing. Here is the recommended implementation:
```javascript decision-handler.js theme={null}
function evaluateVerification(result) {
// Sanctions check is always first, always a hard stop
if (hasSanctionsHit(result)) {
return { action: "HALT", reason: "Sanctions hit detected", escalate: true };
}
// ... other decision logic
}
function hasSanctionsHit(result) {
if (result.screening?.sanctions_status === "Hit") return true;
const entities = [
...(result.directors || []),
...(result.beneficial_owners || [])
];
return entities.some((e) => e.sanctions_status === "Hit");
}
```
When a sanctions hit is detected:
1. **Immediately halt** all processing and onboarding.
2. **Do not auto-approve** under any circumstances, regardless of other check results.
3. **Escalate** to your compliance team and legal counsel.
4. **Document** the match and your response for regulatory audit purposes.
## Screening within Company and Person Verification
Screening runs automatically when you submit a Company or Person Verification request. You do not need to call the screening endpoint separately. Results appear in the `screening` section of the compliance report.
Use the standalone screening endpoint for three scenarios:
**Ongoing monitoring.** Re-screen existing customers on a schedule (monthly, quarterly) to catch new PEP appointments, sanctions designations, or adverse media.
```bash theme={null}
# Re-screen an existing customer
curl -X POST \
"https://instance.prod.onboardapp.io/api/gateway/execute/{project_hash}/screening/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your-api-key" \
-H "X-SYNC-TIMEOUT: 15000" \
-d '{
"name": "John Smith",
"date_of_birth": "1975-03-20",
"country": "GB",
"entity_type": "person",
"categories": ["pep", "sanctions", "adverse_media", "watchlist"],
"external_reference": "MONITOR-2026-Q1-00042"
}'
```
**Pre-checks.** Screen a name before starting a full Company or Person Verification flow to identify obvious blockers early.
**Batch screening.** Screen multiple entities in sequence without running full verification.
## Next steps
Understand execution models and timeouts
Receive screening results via webhook
Handle provider timeouts and failures