HLID API / DOCUMENTATION
API documentation
Verify individuals and companies through HeliumID's hosted flows. Create sessions from your server, send people to a secure link, and receive results by webhook or through the API.
Getting started
#Introduction
HeliumID verifies your end-users and business customers through secure, hosted verification flows. Your server creates a verification session, your user completes it on any device, and HeliumID sends the result to your systems.
HeliumID exposes two verification families:
- Individual verifications via
POST /v1/verifications: document capture, selfie and biometric matching for a single person. - Company verifications via
POST /v1/company-verifications: business registration details, company documents, ownership information and checks on the authorised representative.
Core steps
- Sign in. Go to HeliumID and sign in. If you don't have an account yet, one is created for you.
- Create an API key. Open the Developers page and create a key.
- Choose your integration. Call the API from your server to create sessions, or create sessions manually from the dashboard.
- Create a verification session. Generate a new session for your end-user or business customer.
- Receive verified data. If you have set up webhooks on your API key, you receive the session result and verified data automatically.
- Review the session. If needed, review the session manually in the HeliumID Dashboard.
Individual verification in a nutshell
- Create a verification session. Call
POST /v1/verifications. You receive a hosted link and a verification ID. - Complete verification. The end-user completes the hosted verification flow.
- Receive webhooks or fetch status. Wait for webhook notifications, or call
GET /v1/verifications/{id}to fetch the current status and user data.
Company verification in a nutshell
- Create a company verification. Call
POST /v1/company-verifications. You receive a hosted link, a public verification ID and an expiry timestamp. - The representative completes the hosted flow. They upload company documents, review extracted data and submit.
- Track progress. Follow company webhook events, the dashboard, or
GET /v1/company-verifications/{id}.
#Prerequisites
Before you send your first request, make sure you:
- Have access to the HeliumID Dashboard.
- Have created an API key.
- Have configured the webhook URLs for that API key, if you want automated updates.
- Have your API keys and the base URL at hand.
#Authentication
Every endpoint is authenticated with an API key sent in the x-api-key request header.
To get your key:
- Sign in to the HeliumID Dashboard.
- Open the Developers page from the left navigation bar.
- Generate or copy an active API key.
Base URL
Send every request to the base URL, followed by the endpoint path:
https://server.heliumid.io/api/v1/{endpoint}Request headers
| Header | Required | Description |
|---|---|---|
x-api-key | Always | Your integration's API key. Identifies and authenticates the sender. |
Content-Type | POST and PATCH | Set to application/json. |
Requests with a missing or invalid key return 401 Unauthorized.
#API keys
You use API keys to exchange data with HeliumID. There are two types: test keys and live keys.
Test keys
Use test keys for development. Verification sessions completed with a test key don't count towards your paid usage.
HeliumID doesn't make automatic decisions on test sessions. You trigger the decisions yourself from the dashboard so you can exercise every outcome in your integration.
Live keys
Use live keys in production. A live key requires a working webhook URL. Sessions completed with a live key count towards your paid usage, and HeliumID makes the decisions.
Create an API key
- Sign in to your workspace.
- Open Developers in the left-hand menu.
- Click Add API Key.
- Give the key a suitable name.
- Choose the key type: Test or Live.
Webhook URLs
Each API key can have two webhook destinations: an events webhook URL for progress updates and a decision webhook URL for final outcomes. See Webhooks.
Test integrations
If you have an active HeliumID account, one test integration is created for you. Use it to test communication between your system and HeliumID and to see how to handle responses. You can create more test integrations if you need them; sessions created with them are never billed.
#Quick start
Create your first individual verification with a test key.
1. Create a test API key
In the dashboard, open Developers › Add API Key and choose Test. Add your webhook URLs if you want progress and decision updates.
2. Create a verification
Call POST /v1/verifications from your server. Pass your own user ID as vendorData so you can match the result to your record.
3. Send your user to the link
Redirect the user to data.link exactly as returned. Store data.verificationId alongside your user record.
4. Handle the result
Listen for verification.successful and verification.failed on your decision webhook, or fetch the session with GET /v1/verifications/{id}. With a test key, set the decision yourself from the dashboard.
200, not 201. The JSON body echoes the same value in its code field.#Test vs live
Test and live keys use the same integration shape. What changes is who controls the final decision and whether usage is billed.
| Behaviour | Test API key | Live API key |
|---|---|---|
| Intended use | Development, QA and integration testing | Production |
| Billing | Doesn't count towards paid usage | Counts towards paid usage |
| Webhooks | Supported | Supported |
| HMAC signing | Supported | Supported |
| Customer-admin testing controls | Available where supported | Not available for terminal decisions |
Individual verifications
| Capability | Test | Live |
|---|---|---|
| Create verification sessions | Yes | Yes |
| Receive progress webhook events | Yes | Yes |
| Receive terminal decision webhook events | Yes | Yes |
| Manually update terminal status from the dashboard | Yes | No |
| Use for real production decisions | No | Yes |
Company verifications
| Capability | Test | Live |
|---|---|---|
| Create company verification sessions | Yes | Yes |
| Receive company progress webhook events | Yes | Yes |
| Receive company decision webhook events | Yes | Yes |
| Move a company verification into review from the dashboard | Yes | No |
| Set terminal company decisions from the dashboard | Yes | No |
| Final decision controlled by HeliumID | Not required for testing | Yes |
Workspace admins should not rely on live dashboard controls for terminal company outcomes: live company verifications are reviewed and decided by HeliumID.
Keep these identical in both environments
- Request format
- Webhook signature verification
- Event parsing
- Retry behaviour
- Storage of
vendorData, verification IDs and event logs
Tell your team
Make sure engineering, operations and support know which key environment is in use, whether a decision was test- or live-generated, and whether a company outcome came from HeliumID review or customer-admin testing.
#Testing your integration
Use these checklists before you switch to a live key.
Functional checklist
- A new session link is generated.
- Session data is saved with your record.
- The end-user gets access to your platform after an approved decision.
- The end-user is told about a failed verification after a declined decision.
- Both positive and negative terminal webhook outcomes are handled.
- At the end of verification, the user is redirected back to the right place in your platform.
Security checklist
- Your server rejects a webhook signed with the wrong API key.
- Your server rejects a webhook with a mismatched signature.
- Your server doesn't crash when it receives a webhook with invalid JSON.
Recommended rollout
- Build your integration against a test API key.
- Validate webhook signatures and payload parsing.
- Test both positive and negative decision handling.
- Verify dashboard review behaviour.
- Move to a live key once your webhook and session handling is stable.
Guides
#Individual verification
An individual verification session is the standard way to send an end-user into HeliumID's hosted identity flow.
Create a session with the API
- Send the verification object to
https://server.heliumid.io/api/v1/verifications. - Include the
Content-Type: application/jsonheader. - Include the
x-api-keyheader with your integration's API key. - Include
vendorData. It is strongly recommended so you can identify the session on your side. - Optionally include
metato pass additional custom data. - Optionally set
methodtoauto(the default, automated systems) ormanual(reviewed by an Identity Specialist). - Read
data.linkfrom the response. You need it to bring the end-user into the verification flow.
What to do with the response
- Store
data.verificationIdon your side. - Redirect the end-user to
data.link. - Wait for webhooks, or fetch the session later with
GET /v1/verificationsorGET /v1/verifications/{id}.
How updates are delivered
Progress events go to your events webhook URL: verification.started, verification.selfie_uploaded, verification.document_front_uploaded, verification.document_back_uploaded, verification.processing_started and verification.biometrics_matched.
Terminal outcomes go to your decision webhook URL: verification.successful and verification.failed.
If you prefer to poll, use the fetch endpoints.
Create a session manually
You can also create a session in the dashboard. Use this to test or debug sessions, or as a manual fallback. It creates a link you can share with the end-user.
- Open the HeliumID Dashboard.
- Click Verifications in the left-hand menu.
- Click Create Verification.
- Copy the generated link and share it.
Session creation limits
The number of sessions you can create in an integration is capped according to your HeliumID plan. Contact support if you need a higher limit.
Other languages
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
url := "https://server.heliumid.io/api/v1/verifications"
payload := strings.NewReader(`{"vendorData":"user_12345","method":"auto","meta":{"key":"value"}}`)
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
req.Header.Add("x-api-key", "YOUR_API_KEY")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://server.heliumid.io/api/v1/verifications"))
.header("Content-Type", "application/json")
.header("x-api-key", "YOUR_API_KEY")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"vendorData\":\"user_12345\",\"method\":\"auto\",\"meta\":{\"key\":\"value\"}}"))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}#Individual decisions
When an individual verification reaches a terminal outcome, HeliumID sends a decision webhook to the API key's decision webhook URL. The terminal decision events are verification.successful and verification.failed.
Ways to get a decision
- Dashboard: the Verifications page in the HeliumID Dashboard.
- Decision webhook: configure a webhook URL on your API key to receive automated updates.
- API: call
GET /v1/verifications/{id}to fetch the current status.
What a decision means
| Status | Meaning |
|---|---|
successful | HeliumID verified the user. |
failed | HeliumID could not verify the user. |
cancelled | The verification was cancelled before it completed. |
You may also see in-progress states such as not started and started if you fetch a session before it reaches an outcome. Some records and internal tools also show compatibility states such as completed or abandoned. See Verification statuses.
Session lifecycle
- Created. You create a session through the API or the dashboard.
- Started. The user enters the flow and provides document images and a selfie.
- Processed. HeliumID evaluates the submitted data.
- Decision reached. The session becomes
successfulorfailed.
For richer progress signals before the final outcome, configure your events webhook URL as well. Events such as verification.started and verification.processing_started are sent there.
POST /v1/verifications.#Company verification
Company verification collects business registration details, company documents, ownership information and checks on the authorised representative through HeliumID's hosted company flow.
Before you start
- Create an API key in the HeliumID Dashboard.
- Make the request from your server. Never expose your API key in browser or mobile-app code.
- Configure your API key's webhook URLs if you want automated updates.
- Keep the returned hosted link intact. It contains a secure hosted-flow token and must be used exactly as returned.
Understand the response
The response contains two identifiers with different purposes:
data.linkis the full hosted URL to send the representative to.data.verificationIdis HeliumID's public company verification ID, useful for your own tracking and webhook correlation.
The hosted URL also contains processId (HeliumID's internal company verification record ID) and hostedToken (the secure token that authorises the hosted session). See IDs and terminology.
Send the representative to the hosted flow
Redirect the company's authorised representative to data.link. The hosted page shows your workspace name and logo in its Requested by card.
The representative completes these steps:
- Upload company documents.
- Review AI-assisted extraction results.
- Confirm company details.
- Review ownership and control details.
- Complete the authorising-person section.
- Provide additional compliance information.
- Review and submit the company verification.
Depending on your workspace configuration, the flow can also include a hosted selfie liveness check for the authorising person.
Use the returned link exactly as-is
- Don't replace
processIdwithvendorData, your own customer ID or the publicverificationId. - Don't strip
hostedTokenfrom the URL. - Don't generate your own company verification links on the client.
To resend the representative to the flow, reuse the link you stored or generate a new hosted link from the dashboard.
Track progress
Track company verifications with company webhook events, the dashboard, or GET /v1/company-verifications/{id}.
Progress events go to the events webhook URL: company_verification.submitted, company_verification.liveness_completed and company_verification.under_review. Decisions go to the decision webhook URL: company_verification.successful and company_verification.failed.
Live and test company verifications
- Live: HeliumID handles the final review and terminal decision.
- Test: customer admins can move the verification through review states in the dashboard.
#Company lifecycle
A company verification typically moves through the flow like this:
- You create a company verification with
POST /v1/company-verifications. - The representative opens the hosted link.
- The representative uploads documents, reviews extracted data and submits the flow.
- HeliumID reviews the submission.
- HeliumID issues a final outcome.
Statuses
| Status | What it means |
|---|---|
not started | Created, but the representative hasn't started the hosted flow yet. |
started | The representative has entered the flow and is working through the steps. |
submitted | The representative completed the hosted flow and submitted it for review. |
under review | A reviewer is handling the submission. |
successful | HeliumID approved the company verification. Terminal. |
failed | HeliumID rejected the company verification. Terminal. |
In practice, submitted means the representative has finished their part, under review means a reviewer is handling the case, and successful and failed are the outcomes to treat as final.
Webhook events
Progress events, sent to the events webhook URL, tell you work is moving forward but are not final outcomes:
company_verification.submittedcompany_verification.liveness_completedcompany_verification.under_review
Terminal events are sent to the decision webhook URL:
company_verification.successfulcompany_verification.failed
Payload shape
Company events include a normalised companyProfile block and a review block, so downstream systems such as bank-account or business-customer providers can consume company details in a stable shape. company_verification.successful uses the same shape as company_verification.submitted; the event, status and review values change to reflect the outcome.
Profile fields may come from data entered in the hosted flow, document extraction and normalisation, or internal review enrichment. The review block tells you how ready the payload is, especially when nested fields still depend on review or further evidence. See Company payloads.
What to store
- Your own business or application ID, sent as
vendorData - The
verificationIdreturned by the create response - The webhook
data.companyProfile, if you provision downstream business accounts - The webhook
event - The webhook
data.status
#Reviewing in the dashboard
The HeliumID Dashboard is the main place to inspect a full verification record and the webhooks sent for it.
Individual verifications
- Sign in to the HeliumID Dashboard.
- Open Verifications in the left navigation bar.
- Search by vendor data or verification ID, or use the filters.
- Open the verification.
You can review the verification status, extracted document data, uploaded media, the decision reason for failed sessions, and the environment and API key context.
Company verifications
- Sign in to the HeliumID Dashboard.
- Open Verifications.
- Switch to the company verifications view.
- Search by company name,
vendorDataor company verification ID. - Open the record.
The detail view can include:
- Current verification status
- Company profile information
- Representative and authorising-person details
- Uploaded company documents
- AI-assisted extracted company data
- Ownership and beneficial-owner information
- Additional compliance information
- The redirect URL and webhook URL stored on the verification
- Campaign metadata, when the verification was created from a company verification campaign
Webhook payloads and deliveries
- Open Events in the dashboard.
- Find the relevant
verification.orcompany_verification.event. - Open the event to inspect the exact payload HeliumID sent.
- Switch to the Webhooks view to inspect delivery attempts, retries and response failures.
This is useful to confirm which event was emitted, what JSON was sent and whether delivery succeeded, and to debug sandbox or test-key integrations.
Review permissions
- Live company verifications: HeliumID makes the final review and terminal decision internally.
- Test company verifications: workspace admins can move the verification into review states and terminal outcomes to test the full integration loop.
#Recipe: individual integration
The simplest reliable pattern for individual verification.
What you'll build
- Create a verification from your server.
- Store the returned
verificationId. - Redirect the user to the hosted link.
- Receive webhook updates.
- Mark the user as approved or rejected when a decision arrives.
Step 1: Create the verification
Call POST /v1/verifications and store data.verificationId, data.link and your own vendorData.
Step 2: Redirect the user
Send the user to data.link exactly as returned. Don't rebuild the hosted URL yourself.
Step 3: Listen for webhooks
At minimum, handle verification.successful and verification.failed. You can also listen for progress events such as verification.started and verification.processing_started.
Step 4: Verify the signature
Validate the Webhook-Timestamp and Webhook-Signature headers against the raw request body. See Signature verification.
Step 5: Update your system
- On
verification.successful, mark the user as verified. - On
verification.failed, mark the user as rejected or request a new session.
Optional polling fallback
If you need a fallback, fetch the current state with GET /v1/verifications or GET /v1/verifications/{id}.
Recommended stored fields
verificationIdvendorData- Current status
- Latest webhook event
- Raw webhook payload
- Environment used
#Recipe: company integration
The recommended pattern for company verification.
What you'll build
- Create a company verification from your server.
- Store the public company verification ID.
- Send the representative to the hosted link.
- Handle progress and decision webhooks.
- Reconcile the result in your own system.
Step 1: Create the company verification
Call POST /v1/company-verifications and store data.verificationId (the public company verification ID), data.link and your own vendorData.
Step 2: Send the representative to the hosted flow
Use data.link exactly as returned. It contains processId (the internal company verification ID) and hostedToken (secure access to the hosted flow). Don't rebuild it.
Step 3: Listen for company webhooks
Progress events are company_verification.submitted, company_verification.liveness_completed and company_verification.under_review. Decision events are company_verification.successful and company_verification.failed.
Step 4: Reconcile IDs correctly
- Create-response
verificationIdis the public company verification ID. - Webhook
companyVerificationIdis the internal company verification ID. - Webhook
externalIdis the public company verification ID.
For most customer-facing systems, externalId is the most useful long-term reference.
Step 5: Apply review logic
- On
company_verification.submitted, mark the case as awaiting review. - On
company_verification.under_review, mark the case as in review. - On
company_verification.successful, mark the business as approved. - On
company_verification.failed, mark the business as rejected and store the reason.
Recommended stored fields
- Public
verificationIdfrom the create response - Internal
companyVerificationId, when it first arrives by webhook externalIdvendorData- Current status
- Latest webhook event
- Raw webhook payload
- Environment used
In test, customer admins can move company verifications through review and terminal states. In live, HeliumID controls the final decision. See Test vs live.
API reference
#Responses and errors
Every response uses a consistent JSON envelope. The HTTP status code is in the response line and the same number is echoed in the JSON code field. The business payload is inside data.
Success envelope
{
"status": true,
"message": "Created verification",
"data": {},
"code": 200
}Error envelope
Some validation errors also include an error object or extra metadata, depending on the endpoint.
{
"status": false,
"message": "Verification not found",
"code": 404
}HTTP status codes
| Code | Description | Why you might see it |
|---|---|---|
200 | OK | Successful requests, including the current create endpoints. |
201 | Created | May be used by resource-creation endpoints. Don't assume every create returns 201; check each endpoint. |
400 | Bad Request | The payload, parameters or business rules are invalid. For example a malformed URL, an invalid verification ID, or an update in an unsupported state. |
401 | Unauthorized | Authentication failed: a missing or invalid API key, or an invalid hosted-flow token. |
404 | Not Found | The requested resource can't be found. |
500 | Internal Server Error | Something went wrong on HeliumID's side. |
Practical notes
- Treat any non-2xx response as a failed call, even if the body contains more detail.
- Don't rely on
201for create operations. The verification and company verification create endpoints return200. - Read the JSON
messagefield for a human-readable explanation when a request fails.
#Pagination
List endpoints are paginated with page numbers. Pass page (starting at 1) and limit (default 10) as query parameters. Each list response includes a meta object.
| Field | Type | Description |
|---|---|---|
total | integer | Total number of records. |
page | integer | The current page. |
limit | integer | Records per page. |
totalPages | integer | Total number of pages. |
Keep requesting the next page until page equals totalPages.
#List verifications
GET/v1/verifications
Fetches the individual verifications created by the current API key. Results are paginated and don't include deleted records. Each item is a verification object.
Query parameters
pageintegerOptional- Page number, starting at
1. Default1. limitintegerOptional- Maximum number of records per page. Minimum
1, default10.
#Create a verification
POST/v1/verifications
Creates an individual verification session and returns a hosted verification link for the end-user. The request body is optional, but sending vendorData is strongly recommended.
Body parameters
vendorDatastringOptional- Your own correlation identifier, such as your user ID. Echoed back in webhooks and verification records.
metaobjectOptional- Additional custom data to store with the verification. Stored as
userMeta. methodstringOptionalauto(default) uses automated systems.manualroutes the session to review by an Identity Specialist.
Store data.verificationId and redirect the user to data.link exactly as returned. For individual verifications, verificationId is the same value as the processId in the hosted link.
#Retrieve a verification
GET/v1/verifications/{id}
Fetches the current state of a single individual verification created by the current API key. Returns a verification object.
Path parameters
idstringRequired- The verification ID returned when the session was created.
If you get 404 Not Found, check that you are using an individual verification ID (not a company verification ID), that the verification belongs to the same API key, and that it hasn't been deleted.
#Cancel a verification
PATCH/v1/verifications/{id}/cancel
Cancels an individual verification that hasn't yet reached a terminal successful or failed outcome. The session moves to cancelled (5000) and the user can't continue with it.
Path parameters
idstringRequired- The verification ID returned when the session was created.
Cancelling a verification that is already terminal returns 400 Bad Request.
#Delete a verification
DELETE/v1/verifications/{id}
Soft-deletes an individual verification. Deleted records no longer appear in fetch results for the current API key.
Path parameters
idstringRequired- The verification ID returned when the session was created.
#The verification object
Returned by the individual verification fetch and cancel endpoints.
| Field | Type | Description |
|---|---|---|
id | string | The verification ID. Same value as verificationId and the hosted-link processId. |
status | string | One of not started, started, abandoned, completed, successful, failed, cancelled. See Verification statuses. |
statusCode | integer | Numeric counterpart of status, for example 2010. |
reason | string | Failure reason, or an empty string. |
vendorData | string | Your correlation identifier. |
userMeta | object | The meta object you sent when creating the session. |
userEmail | string | The end-user's email, when available. |
verificationMethod | string | auto or manual. |
selfie | object | Selfie storage key and url. |
doc | object | Document type, name, issuingState, and key and url objects with front and back. |
docInfo | object | Data extracted from the document. See below. |
createdAt | string | ISO 8601 creation time. |
updatedAt | string | ISO 8601 time of the last update. |
docInfo
| Field | Type | Description |
|---|---|---|
docType | string | Document type, for example NATIONAL_ID or PASSPORT. |
docNumber | string | Document number. |
issueDate | string | Issue date, if available. |
expiryDate | string | Expiry date, if available. |
issuingState | string | Issuing country or state code. |
firstName | string | First name. |
lastName | string | Last name. |
middleName | string | Middle name. |
dob | string | Date of birth. |
sex | string | Sex, as printed on the document. |
#List company verifications
GET/v1/company-verifications
Fetches the company verifications created by the current API key. Results are paginated and ordered by most recently updated. Each item is a company verification object.
Query parameters
pageintegerOptional- Page number, starting at
1. Default1. limitintegerOptional- Maximum number of records per page. Minimum
1, default10.
#Create a company verification
POST/v1/company-verifications
Creates a company verification session and returns a hosted link for the authorised representative. The returned verificationId is the public company verification ID. The processId inside the link is the internal company verification record ID.
Body parameters
vendorDatastringOptional- Your own identifier, such as a business or application ID.
metaobjectOptional- Additional metadata to store with the company verification.
campaignIdstringOptional- A company verification campaign ID, in the format
cmp_followed by 24 hex characters. When set, the session uses that campaign's published configuration.
The response also includes campaignVersion (the published campaign version used) and expiresAt (when the hosted link expires). Treat data.link as opaque: never rebuild it or remove its query parameters. See Company verification.
#Retrieve a company verification
GET/v1/company-verifications/{id}
Fetches the current state of a single company verification created by the current API key. Returns a company verification object.
Path parameters
idstringRequired- Either the internal company verification ID or the public company verification ID returned as
verificationIdwhen the session was created.
#Delete a company verification
DELETE/v1/company-verifications/{id}
Deletes a company verification created by the current API key.
Path parameters
idstringRequired- Either the internal company verification ID or the public company verification ID returned as
verificationId.
#The company verification object
Returned by the company verification fetch endpoints. Fields other than the identifiers and status are only present once the relevant data has been collected.
| Field | Type | Description |
|---|---|---|
id | string | Internal company verification ID. Same value as the hosted-link processId and the webhook companyVerificationId. |
verificationId | string | Public company verification ID. Same value as externalId. |
externalId | string | Public company verification ID, for example cv_…. |
status | string | One of not started, started, submitted, under review, abandoned, successful, failed. |
reason | string | Rejection reason, or an empty string. |
vendorData | string | Your correlation identifier. |
userMeta | object | The meta object you sent when creating the session. |
redirect | string | The redirect URL stored on the verification. |
webhookUrl | string | The webhook URL stored on the verification. |
campaignId | string | Campaign ID, if created from a campaign. |
campaignVersion | integer | Published campaign version used. |
businessProfile | object | Business details, such as companyName and countryOfRegistration. |
representativeProfile | object | Representative details, such as legalFirstName and legalSurname. |
documents | object | Named document slots. |
uploadedDocuments | array | Uploaded document descriptors. |
extractedProfile | object | AI-assisted data extracted from documents. |
ownershipProfile | object | Shareholders, directors and beneficial owners. |
authorityProfile | object | Authority declarations and liveness status, such as livenessStatus and livenessVerificationId. |
additionalInformation | object | Additional compliance information. |
analysis | object | Document analysis state. |
progress | object | Flow progress, such as currentStep and submittedAt. |
auditTrail | array | Audit entries for the verification. |
customQuestionAnswers | array | Answers to campaign custom questions. |
createdAt | string | ISO 8601 creation time. |
updatedAt | string | ISO 8601 time of the last update. |
companyProfile instead of these raw profile objects. Use company webhook payloads when you provision downstream accounts.Webhooks
#Overview
HeliumID uses webhooks to notify your integration when verification progress changes or a final outcome is reached. Every webhook uses the same outer JSON shape: event tells you what happened and data holds the payload.
{
"event": "verification.successful",
"data": {}
}Destinations
Each API key can be configured with two webhook destinations:
- Events webhook URL: progress updates that keep your UI or internal workflow in sync.
- Decision webhook URL: final outcomes, when a verification reaches a terminal result.
If you only care about final outcomes, implement the decision webhook first. You can listen to it alone.
Headers
HeliumID signs every webhook with the Webhook-Timestamp and Webhook-Signature headers. See Signature verification.
Handling webhooks reliably
- Respond with a
2xxstatus quickly. - Verify the signature against the raw request body before you trust the payload.
- Ignore unknown fields and unfamiliar event types without throwing errors.
- Store the raw payload for auditing and replay.
What to store
- The webhook
event - The webhook
data - Your own correlation key,
vendorData - The HeliumID verification ID
- The API key environment used to create the verification
#Individual events
These events apply to sessions created with POST /v1/verifications.
| Event | Destination | Terminal | Meaning |
|---|---|---|---|
verification.started | Events | No | The end-user entered the hosted verification flow. |
verification.selfie_uploaded | Events | No | The end-user uploaded a selfie. |
verification.document_front_uploaded | Events | No | The end-user uploaded the front of their document. |
verification.document_back_uploaded | Events | No | The end-user uploaded the back of their document. |
verification.processing_started | Events | No | HeliumID started evaluating the submitted data. |
verification.biometrics_matched | Events | No | The selfie matched the face on the document. |
verification.successful | Decision | Yes | The verification was approved. |
verification.failed | Decision | Yes | The verification was rejected. |
See Individual payloads for every field.
#Company events
These events apply to sessions created with POST /v1/company-verifications.
| Event | Destination | Terminal | Meaning |
|---|---|---|---|
company_verification.submitted | Events | No | The representative completed and submitted the hosted flow. |
company_verification.liveness_completed | Events | No | The authorising-person liveness step completed. |
company_verification.under_review | Events | No | The company verification moved into review. |
company_verification.successful | Decision | Yes | The company verification was approved. |
company_verification.failed | Decision | Yes | The company verification was rejected. |
Company events include a normalised companyProfile block and a review block, so systems such as banking or account-opening providers can consume business, contact, officer, ownership and authority details without depending on internal form structures. These objects are part of the payload contract; what can vary is how complete the nested fields are at a given moment.
company_verification.successful has the same shape as the submitted example. The event name, status and review values reflect the approved state. See Company payloads.
#Signature verification
HeliumID signs every webhook with an HMAC-SHA256 signature in the Webhook-Signature header, so you can confirm the payload came from HeliumID and wasn't tampered with. Your API key is the signing secret.
The signature payload
The signed string is the timestamp and the raw JSON body joined by a period:
{timestamp}.{payload}timestampis the UNIX epoch time in milliseconds, taken exactly from theWebhook-Timestampheader.payloadis the exact JSON body of the webhook, containingeventanddata.
Verify a signature
- Read the raw request body before any JSON parsing.
- Build the signature payload as
{timestamp}.{rawBody}. - Compute an HMAC-SHA256 hex digest using your API key as the secret.
- Compare it to
Webhook-Signaturewith a constant-time comparison.
Best practices
- Hash the raw body. Re-serialising a parsed object can change whitespace or key order and break the signature.
- Prevent timing attacks. Use
crypto.timingSafeEqualin Node.js,hmac.compare_digestin Python orsubtle.ConstantTimeComparein Go. - Reject replays. Compare the timestamp with your server's clock and reject requests outside a tolerance window, such as 5 minutes.
- Protect your API key. Keep it in environment variables, never in client-side code.
Go example
package main
import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"io"
"net/http"
"os"
)
func heliumWebhook(w http.ResponseWriter, r *http.Request) {
rawBody, _ := io.ReadAll(r.Body)
timestamp := r.Header.Get("Webhook-Timestamp")
received := r.Header.Get("Webhook-Signature")
mac := hmac.New(sha256.New, []byte(os.Getenv("HELIUM_ID_API_KEY")))
mac.Write([]byte(timestamp + "." + string(rawBody)))
expected := hex.EncodeToString(mac.Sum(nil))
if subtle.ConstantTimeCompare([]byte(expected), []byte(received)) != 1 {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
// Parse rawBody and handle the event
w.WriteHeader(http.StatusOK)
}#Individual payloads
Field-by-field reference for webhooks sent for sessions created with POST /v1/verifications.
verification.started
| Field | Type | Required | Notes |
|---|---|---|---|
verificationId | string | Yes | Internal verification record ID. |
status | string | Yes | Current status, started. |
statusCode | number | Yes | Numeric status code, 2000. |
vendorData | string | No | Your correlation value, if supplied when the session was created. |
verification.selfie_uploaded
| Field | Type | Required | Notes |
|---|---|---|---|
verificationId | string | Yes | Internal verification record ID. |
selfieKey | string | No | Storage key for the uploaded selfie. |
vendorData | string | No | Your correlation value, if supplied. |
verification.document_front_uploaded
| Field | Type | Required | Notes |
|---|---|---|---|
verificationId | string | Yes | Internal verification record ID. |
documentType | string | Yes | Document type selected in the flow. |
issuingState | string | Yes | Document issuing country or state code. |
vendorData | string | No | Your correlation value, if supplied. |
verification.document_back_uploaded
| Field | Type | Required | Notes |
|---|---|---|---|
verificationId | string | Yes | Internal verification record ID. |
documentType | string | Yes | Document type stored on the verification. |
vendorData | string | No | Your correlation value, if supplied. |
verification.processing_started
| Field | Type | Required | Notes |
|---|---|---|---|
verificationId | string | Yes | Internal verification record ID. |
vendorData | string | No | Your correlation value, if supplied. |
verification.biometrics_matched
| Field | Type | Required | Notes |
|---|---|---|---|
verificationId | string | Yes | Internal verification record ID. |
vendorData | string | No | Your correlation value, if supplied. |
verification.successful
| Field | Type | Required | Notes |
|---|---|---|---|
verificationId | string | Yes | Internal verification record ID. |
vendorData | string | No | Your correlation value, if supplied. |
statusCode | number | Yes | Numeric decision code, 2010. |
docInfo | object | Yes | Parsed document information. See below. |
userSelfie | string | No | URL of the stored selfie image. |
userIdBack | string | No | URL of the back document image, when available. |
userIdFront | string | No | URL of the front document image. |
docInfo
| Field | Type | Required | Notes |
|---|---|---|---|
docType | string | Yes | Document type. |
docNumber | string | No | Extracted document number. |
issueDate | string | No | Extracted issue date, if available. |
expiryDate | string | No | Extracted expiry date, if available. |
issuingState | string | No | Extracted issuing country or state. |
firstName | string | No | Extracted first name. |
lastName | string | No | Extracted last name. |
middleName | string | No | Extracted middle name. |
dob | string | No | Extracted date of birth. |
sex | string | No | Extracted sex value. |
verification.failed
| Field | Type | Required | Notes |
|---|---|---|---|
verificationId | string | Yes | Internal verification record ID. |
reason | string | Yes | Failure reason stored on the verification. |
vendorData | string | No | Your correlation value, if supplied. |
statusCode | number | Yes | Numeric decision code, 4100. |
#Company payloads
Field-by-field reference for webhooks sent for sessions created with POST /v1/company-verifications.
Shared company fields
Most company events include this common shape.
| Field | Type | Required | Notes |
|---|---|---|---|
companyVerificationId | string | Yes | Internal company verification record ID. |
verificationId | string | Yes | Public company verification ID. Matches the verificationId returned by the create response. |
externalId | string | Yes | Public company verification ID, for example cv_…. |
vendorData | string | No | Your correlation value, if supplied. |
status | string | Yes | Current company verification status. |
reason | string | No | Rejection reason, or an empty string. |
submittedAt | string | No | ISO timestamp of submission, when available. |
campaignId | string | No | Campaign ID, if created from a campaign. |
campaignVersion | number | No | Campaign version, if available. |
companyProfile | object | Yes | Normalised company data for downstream integrations such as account-opening and banking providers. Nested fields may be empty if the data hasn't been collected or confirmed yet. |
review | object | Yes | Readiness and review metadata explaining how complete or review-dependent the profile is. |
Events
| Event | Payload |
|---|---|
company_verification.submitted | Shared fields, with companyProfile and review. |
company_verification.under_review | Shared fields. |
company_verification.successful | Shared fields, with companyProfile and review. Same shape as submitted, with values describing the successful outcome. |
company_verification.failed | Shared fields. reason contains the rejection reason. |
company_verification.liveness_completed | Shared fields, plus livenessVerificationId (string, required: the linked individual verification used for liveness) and userSelfie (string: URL of the selfie captured during liveness). |
companyProfile.business
| Field | Type | Notes |
|---|---|---|
legalName | string | Company legal name. |
tradingName | string | Trading or operating name, when available. |
registrationNumber | string | Company registration number, for example an RC number. |
registrationType | string | Business or registration type. |
countryOfRegistration | string | Country of incorporation or registration. |
dateOfRegistration | string | Registration date, when available. |
businessBvn | string | Business-level BVN, when explicitly collected or confirmed. Distinct from officers' personal BVNs. |
taxId | string | Tax identifier or TIN, where available. |
industry | string | Industry or sector. |
description | string | Business activity or description. |
website | string | Business website. |
companyProfile.contact
| Field | Type | Notes |
|---|---|---|
emails.general | string | Primary company email. |
emails.alternate | string | Alternate company email, when available. |
emails.representative | string | Representative email. |
phoneNumbers.general | string | Primary business phone number. |
phoneNumbers.alternate | string | Alternate business phone number. |
phoneNumbers.representative | string | Representative phone number. |
addresses.operating | object | Operating address. |
addresses.registered | object | Registered address. |
addresses.representativeResidential | object | Representative residential address, when captured. |
companyProfile.authorisedRepresentative
| Field | Type | Notes |
|---|---|---|
firstName | string | First name. |
middleName | string | Middle name. |
lastName | string | Surname. |
fullName | string | Full name. |
email | string | Email. |
phoneNumber | string | Phone number. |
nationality | string | Nationality. |
countryOfResidence | string | Country of residence. |
relationshipToBusiness | string | Role or relationship to the business. |
isAuthorised | string | Whether the representative states they are authorised. |
ownsPartOfBusiness | string | Whether the representative states they own part of the business. |
shareholdingPercentage | string | Shareholding percentage, when declared. |
identityDocumentType | string | ID document type. |
documentNumber | string | ID document number. |
issuingCountry | string | ID issuing country. |
expiryDate | string | ID expiry date. |
dateOfBirth | string | Date of birth. |
residentialAddress | object | Residential address, or the best available address string. |
companyProfile.officers
An array. Each officer can include:
| Field | Type | Notes |
|---|---|---|
firstName | string | First name. |
middleName | string | Middle name. |
lastName | string | Surname. |
fullName | string | Full name. |
email | string | Email. |
phoneNumber | string | Phone number. |
role | string | Normalised role, such as director, owner or authorised_representative. |
roles | string[] | Additional normalised roles, when applicable. |
rawRole | string | Original self-declared role text, where available. |
nationality | string | Nationality. |
countryOfResidence | string | Country of residence. |
shareholdingPercentage | string | Ownership share or declared allocation. |
identityDocumentType | string | ID document type. |
documentNumber | string | ID document number. |
issuingCountry | string | ID issuing country. |
expiryDate | string | ID expiry date. |
dateOfBirth | string | Date of birth. |
bvn | string | BVN, when captured. |
address | object | Best available structured or full-text address. |
source | string | Source of the officer row, for example self_reported. |
companyProfile.ownership
| Field | Type | Notes |
|---|---|---|
threshold | number | Beneficial ownership threshold used for derivation. |
shareholders | object[] | Shareholder rows from the ownership profile. |
directors | object[] | Director rows from the ownership profile. |
beneficialOwners | object[] | Beneficial owners derived from the ownership profile. |
unresolvedCorporateOwners | string[] | Material corporate owners that may still need deeper beneficial-owner look-through. |
companyProfile.authority
| Field | Type | Notes |
|---|---|---|
role | string | Role declared in the authority section. |
isAuthorised | string | Whether authority has been declared. |
isDirector | string | Whether the representative is a director. |
additionalSignatoryRequired | string | Whether extra authority evidence is needed. |
livenessStatus | string | Current liveness status. |
livenessVerificationId | string | Linked individual liveness verification ID. |
livenessStartedAt | string | Liveness start timestamp. |
livenessCompletedAt | string | Liveness completion timestamp. |
companyProfile.additionalInformation
| Field | Type | Notes |
|---|---|---|
purposeOfRelationship | string | Declared purpose of the relationship. |
expectedActivity | string | Expected activity summary. |
expectedTransactionVolume | string | Expected transaction volume. |
sourceOfFunds | string | Source of funds. |
sourceOfWealth | string | Source of wealth. |
countriesOfOperation | string[] | Countries of operation. |
regulatoryStatus | string | Regulatory status, when provided. |
regulatoryLicences | string | Regulatory licence information. |
companyProfile.documents
| Field | Type | Notes |
|---|---|---|
provided | object | Named document slots, such as CAC, MEMART, Form 1 or proof of address. |
uploaded | object[] | Uploaded document descriptors, including file name, type, key, URL and upload time where available. |
review
Tells downstream consumers whether the payload is ready for provisioning or still needs verification or review.
| Field | Type | Notes |
|---|---|---|
source | string | High-level source of the current profile: self_reported, document_enriched or mixed. |
analysisStatus | string | Current document-analysis status. |
currentStage | string | Current analysis stage, when available. |
provider | string | Analysis provider name, when available. |
pendingInternalReview | boolean | Whether the company verification is still awaiting review work. |
missingFields | string[] | Important fields still missing from the normalised profile. |
reviewFlags | string[] | Flags raised by normalisation or document analysis. |
followUp | object | Follow-up guidance, such as missing evidence, blocking fields or requested documents. |
provisioning | object | Quick readiness summary for downstream provisioning. See below. |
review.provisioning
| Field | Type | Notes |
|---|---|---|
ready | boolean | true when the profile is considered ready for downstream provisioning. |
pendingFields | string[] | Important business, representative or officer data still missing. |
companyProfile and review fields are optional. HeliumID may add new optional fields over time, so ignore unknown fields safely.Reference
#Verification statuses
Individual and company verifications have different lifecycles. status is a string and statusCode is its numeric counterpart.
Individual verifications
These statuses appear in the decision webhook, GET /v1/verifications and GET /v1/verifications/{id}.
| Status | Code | Sent via | Meaning |
|---|---|---|---|
not started | 1001 | API | A session was created. The end-user hasn't entered the flow yet. |
started | 2000 | Webhook, API | The end-user started the session. No decision yet. |
abandoned | 3033 | Webhook, API | The end-user started but never finished. A session is abandoned 7 days after creation unless it reaches a conclusive decision first. Complete. |
completed | 2001 | Webhook, API | The end-user's required data was submitted and processed. |
successful | 2010 | Webhook, API | Positive: the end-user was verified. Complete. |
failed | 4100 | Webhook, API | Negative: the end-user wasn't verified, either a fraud case or another severe reason. Complete. Investigate the session further. |
cancelled | 5000 | Webhook, API | The session was explicitly cancelled before it completed. |
2010, 4100, 3033 and 5000 are conclusive. Once a session reaches one of them it is closed. To give the user another try, create a new session with POST /v1/verifications.
Expiry and abandonment
All individual verification sessions expire after 7 days:
- Sessions still
not startedmove tocancelled. - Sessions still
startedmove toabandoned.
Company verifications
These statuses appear in company webhook payloads, the company verification fetch endpoints and the dashboard.
| Status | Code | Delivered through | Meaning |
|---|---|---|---|
not started | 1001 | Dashboard, API | Created, but the representative hasn't started the hosted flow. |
started | 2002 | Dashboard, API | The representative started the hosted flow. |
submitted | 2003 | Webhook, dashboard, API | The representative completed and submitted the flow for review. |
under review | 2004 | Webhook, dashboard, API | The submission is being reviewed. |
abandoned | 3033 | Dashboard, API | Defined on the model, but not part of the main public lifecycle. |
successful | 2060 | Webhook, dashboard, API | Positive: HeliumID approved the company verification. |
failed | 4004 | Webhook, dashboard, API | Negative: HeliumID rejected the company verification. |
The most important company milestones for integrations are submitted, under review, successful and failed. Live company verifications are decided by HeliumID; test company verifications can be moved through review states from the dashboard.
#IDs and terminology
HeliumID uses a few identifiers that sound similar but serve different purposes. The biggest difference is between individual and company flows.
Individual verifications
verificationId is the main verification record ID. It appears in the POST /v1/verifications response, the GET /v1/verifications/{id} path, individual webhook payloads, and the hosted link as processId. For individual verifications, verificationId and processId are the same value.
https://verify.heliumid.io/?processId=69d4f7657bcbe3b58b96b2f7Company verifications
Company verifications use two identifiers on purpose:
- Public ID: the
verificationIdreturned byPOST /v1/company-verifications, for examplecv_0b5f6f4e0aab25d8c130ab21. Stored asexternalId. Use it for customer-facing references, webhook correlation and support workflows. - Internal ID:
companyVerificationIdin webhooks. It is also theprocessIdin the hosted link.
https://verify.heliumid.io/company?processId=69d4f7657bcbe3b58b96b2f7&hostedToken=...Other terms
externalId: the public company verification ID. Returned asverificationIdon create and included in company webhooks. Not used as the hostedprocessId.vendorData: your own correlation value, such as a user, business, application or onboarding case ID. Stored and echoed back in webhooks and records. Use it as your primary correlation key.meta: extra structured information attached on create, stored asuserMeta. Use it for context, workflow hints and integration metadata, not as your only correlation key.campaignIdandcampaignVersion: the company verification campaign and the published version used to create the session. Returned on create and echoed in webhooks when present.hostedToken: authorises access to the hosted company flow. Treat the full link as opaque, store or forward it exactly as returned, and never remove the token.
Quick mapping
| Term | Flow | Meaning |
|---|---|---|
verificationId | Individual | Main verification record ID and hosted processId |
verificationId | Company | Public company verification ID, also called externalId |
companyVerificationId | Company | Internal company verification record ID |
processId | Individual | Verification ID used by the hosted link |
processId | Company | Internal company verification ID used by the hosted link |
externalId | Company | Public company verification ID |
vendorData | Both | Your own correlation identifier |
meta | Both | Additional custom metadata |
campaignId | Company | Campaign identifier |
campaignVersion | Company | Campaign version used |
hostedToken | Company | Secure hosted-flow token |
Recommended storage
For individual verifications, store verificationId, vendorData and the environment used.
For company verifications, store the public verificationId from the create response, the internal companyVerificationId when it first arrives by webhook, vendorData and the environment used.
#Backwards compatibility
HeliumID considers the following changes backwards compatible. Build your systems to handle them gracefully.
- Adding new properties (strings, objects, arrays) to existing API responses.
- Changing the order of properties in existing API responses.
- Adding new API resources, such as new endpoints.
- Adding new optional request parameters to existing endpoints.
- Changing the length or format of opaque strings, such as error messages and object IDs.
- Adding new event types to webhooks.
- Adding new properties to webhook payloads.
Property order is not static. The order in your actual responses may differ from the order shown in this documentation.
Opaque strings are values your system should store or pass on, not interpret or parse for logic.
#Troubleshooting
I'm not receiving webhooks
- Confirm the API key has the correct events and decision webhook URLs.
- Confirm your endpoint is publicly reachable.
- Confirm your server returns a
2xxresponse quickly. - Confirm you are listening for the right event names.
- Confirm you are testing with the expected API key environment.
In the dashboard, open Events to see whether the event was created, and Webhooks to inspect delivery attempts and failures. If the event exists but wasn't delivered, the issue is usually the webhook URL or the receiving server.
The webhook signature doesn't validate
Common causes are using the wrong API key as the secret, hashing a parsed JSON object instead of the raw body, missing the exact Webhook-Timestamp value, or changing whitespace or key order before hashing. Follow the steps in Signature verification.
My company verification is stuck in started
The representative began the hosted flow but hasn't submitted it. Check whether all required company documents were uploaded, all required sections were completed, and whether the authorising-person liveness step is still pending. Once submitted, the verification moves to submitted and emits company_verification.submitted.
My company verification is submitted but has no decision
That is expected until review completes. The case may be waiting for review or not yet moved to under review. If you are testing, you may not have advanced the decision in the test dashboard yet. Live company verifications are decided by HeliumID.
The hosted link doesn't open
For individual verifications, confirm you are using the exact data.link returned by the create endpoint.
For company verifications, confirm the URL still contains both processId and hostedToken, that the link hasn't expired, and that you didn't rebuild it. If the link has expired, generate a new one from the dashboard.
GET /v1/verifications/{id} returns not found
Check that you are using an individual verification ID rather than a company verification ID, that the verification belongs to the same API key, and that it hasn't been deleted. Fetch company verifications with GET /v1/company-verifications/{id}.
I can update a test company verification but not a live one
That is expected. Customer-admin review and terminal status updates are limited to company verifications created with a test key. Live terminal decisions are reserved for HeliumID's internal review.
I'm seeing the wrong ID in webhooks
Individual webhooks use verificationId. Company webhooks use companyVerificationId for the internal record and externalId for the public ID. See IDs and terminology.
Events webhook or decision webhook?
Use the events webhook for progress updates and the decision webhook for terminal approval or rejection. If you only care about final outcomes, implement the decision webhook first.
Support checklist
When you contact support about an integration issue, include:
- The API key environment used
- The verification type: individual or company
- Your
vendorData - The HeliumID verification ID
- The event name
- The raw webhook payload
- The response status your server returned to the webhook
#Changelog
Notable API, webhook and lifecycle changes that might affect your webhook consumers, status handling or dashboard workflows.
18 August 2026
Company verification webhook events. Company verification now uses dedicated event names: company_verification.submitted, company_verification.liveness_completed, company_verification.under_review, company_verification.successful and company_verification.failed.
Expanded company webhook payloads. Company webhooks now include a normalised companyProfile block and a review block covering business registration, contact details, the authorised representative, officers and ownership, and provisioning readiness.
Company lifecycle clarified. The documented lifecycle is not started, started, submitted, under review, successful and failed.
Test and live decisions clarified. Test company verifications can be moved through review and terminal states from the dashboard. Live company verifications keep final decisions under HeliumID's control.
Company IDs clarified. The documentation now distinguishes the public verificationId, the internal companyVerificationId, the hosted-flow processId and the public externalId.
Create responses clarified. Verification create endpoints return HTTP 200 with a JSON body that also includes code: 200.
Notes for integrators
- Treat webhook payloads as extensible. New optional fields and event types may be added.
- Handle unknown event names gracefully.
- Store raw webhook payloads for replay and audit.
Ask the docs
Answers from this documentation
AI answers can be wrong. Check the linked sections.