Tracking Progress
When creating the flow using the call to our API, the response of that call contains a token. You can use this token to track progress and to fetch (validated) data from the API. The response looks as follows:
{
"checkToken": "abcdef",
"readyForProfessional": false
}
The token should be saved on your side, in order to use it later on to query the progress. The readyForProfessional indicates whether the flow can be started by the professional. If it is false, please wait for the READY callback before providing the professional with the link to the screening.
Retrieving Flow Status
You can use the token and the following endpoint to query the progress and (validated) data:
GET https://[api-url]/validation-flow/check/[token]
Accept: application/json
Authorization: Bearer YOUR_API_KEY
Authorization-Method: api-token
Complete Response Structure
You'll get back the following structure:
{
"id": 123,
"dateCreated": "2023-12-18T13:44:25.471306Z",
"dateModified": "2023-12-18T15:57:27.505264Z",
"recruiterFirstName": "Test",
"recruiterLastName": "Kees",
"recruiterEmail": "test.kees@otentica.nl",
"recruiterCompanyName": "Test",
"professionalFirstName": "Kandidaat",
"professionalLastName": "Test",
"professionalEmail": "kandidaat.test@domain.tld",
"professionalPhone": "0612345678",
"status": "ALL_STEPS_DONE",
"canFinish": false,
"finished": true,
"checks": [
{
"checkIdentifier": "identity-data",
"status": "SUCCESS",
"contentType": "application/vnd.otentica.singlecheck.check.identity-data+json",
"importance": "SKIPPABLE",
"fields": [],
"results": [
{
"group": null,
"checkName": "identity-data",
"fieldName": "lastName",
"value": "Test",
"verified": true,
"verifiedBy": "idin",
"verifiedOn": "2023-12-18T15:50:43.713299Z"
},
{
"group": null,
"checkName": "identity-data",
"fieldName": "initials",
"value": "T",
"verified": true,
"verifiedBy": "idin",
"verifiedOn": "2023-12-18T15:50:43.674538Z"
},
{
"group": null,
"checkName": "identity-data",
"fieldName": "firstName",
"value": null,
"verified": true,
"verifiedBy": "idin",
"verifiedOn": "2023-12-18T15:50:43.689345Z"
},
{
"group": null,
"checkName": "identity-data",
"fieldName": "lastNamePrefix",
"value": null,
"verified": true,
"verifiedBy": "idin",
"verifiedOn": "2023-12-18T15:50:43.699336Z"
},
{
"group": null,
"checkName": "identity-data",
"fieldName": "dateOfBirth",
"value": "1970-01-01",
"verified": true,
"verifiedBy": "idin",
"verifiedOn": "2023-12-18T15:50:43.724222Z"
},
{
"group": null,
"checkName": "identity-data",
"fieldName": "gender",
"value": "MALE",
"verified": true,
"verifiedBy": "idin",
"verifiedOn": "2023-12-18T15:50:43.734124Z"
},
{
"group": null,
"checkName": "identity-data",
"fieldName": "street",
"value": "Straat",
"verified": true,
"verifiedBy": "idin",
"verifiedOn": "2023-12-18T15:50:43.744029Z"
},
{
"group": null,
"checkName": "identity-data",
"fieldName": "houseNumber",
"value": "1",
"verified": true,
"verifiedBy": "idin",
"verifiedOn": "2023-12-18T15:50:43.753413Z"
},
{
"group": null,
"checkName": "identity-data",
"fieldName": "houseNumberSuffix",
"value": null,
"verified": true,
"verifiedBy": "idin",
"verifiedOn": "2023-12-18T15:50:43.762992Z"
},
{
"group": null,
"checkName": "identity-data",
"fieldName": "postalCode",
"value": "1234AB",
"verified": true,
"verifiedBy": "idin",
"verifiedOn": "2023-12-18T15:50:43.772308Z"
},
{
"group": null,
"checkName": "identity-data",
"fieldName": "city",
"value": "Stad",
"verified": true,
"verifiedBy": "idin",
"verifiedOn": "2023-12-18T15:50:43.781400Z"
},
{
"group": null,
"checkName": "identity-data",
"fieldName": "countryCode",
"value": "NL",
"verified": true,
"verifiedBy": "idin",
"verifiedOn": "2023-12-18T15:50:43.790334Z"
}
],
"configuration": null,
"completedAsExpected": true
},
{
"checkIdentifier": "correspondence-data",
"status": "SUCCESS",
"contentType": "application/vnd.otentica.singlecheck.check.correspondence-data+json",
"importance": "NOT_SKIPPABLE",
"fields": [],
"results": [
{
"group": "naw",
"checkName": "correspondence-data",
"fieldName": "email",
"value": "test@kees.nl",
"verified": false,
"verifiedBy": null,
"verifiedOn": null
},
{
"group": null,
"checkName": "correspondence-data",
"fieldName": "phone",
"value": "0612345678",
"verified": false,
"verifiedBy": null,
"verifiedOn": null
}
],
"configuration": null,
"completedAsExpected": true
},
{
"checkIdentifier": "identity-document-v2",
"status": "SUCCESS",
"contentType": "application/vnd.otentica.singlecheck.check.identity-document-v2+json",
"importance": "SKIPPABLE",
"fields": [],
"results": [
{
"group": null,
"checkName": "identity-document-v2",
"fieldName": "identityDocument",
"value": "e7e5e957-f010-4de7-9423-fe7581965192",
"verified": false,
"verifiedBy": null,
"verifiedOn": null
},
{
"group": null,
"checkName": "identity-document-v2",
"fieldName": "identityDocumentBackside",
"value": "31a2bda0-3722-458c-b84d-1a8316863a5f",
"verified": false,
"verifiedBy": null,
"verifiedOn": null
},
{
"group": null,
"checkName": "identity-document-v2",
"fieldName": "selfie",
"value": "02ba468a-b481-4e30-ac6c-b0ce9af9996a",
"verified": false,
"verifiedBy": null,
"verifiedOn": null
},
{
"group": null,
"checkName": "identity-document-v2",
"fieldName": "identityDocumentType",
"value": "PASSPORT",
"verified": false,
"verifiedBy": null,
"verifiedOn": null
},
{
"group": null,
"checkName": "identity-document-v2",
"fieldName": "identityDocumentNumber",
"value": "AB123456",
"verified": false,
"verifiedBy": null,
"verifiedOn": null
},
{
"group": null,
"checkName": "identity-document-v2",
"fieldName": "personalNumber",
"value": "123456789",
"verified": false,
"verifiedBy": null,
"verifiedOn": null
},
{
"group": null,
"checkName": "identity-document-v2",
"fieldName": "nationality",
"value": "NLD",
"verified": false,
"verifiedBy": null,
"verifiedOn": null
},
{
"group": null,
"checkName": "identity-document-v2",
"fieldName": "identityDocumentDateOfExpiry",
"value": "2030-01-01",
"verified": false,
"verifiedBy": null,
"verifiedOn": null
}
],
"configuration": {
"applicantLevelName": "id-and-liveness"
},
"completedAsExpected": false
},
{
"checkIdentifier": "unverified-diploma-data",
"status": "SKIPPED",
"contentType": "application/vnd.otentica.singlecheck.check.unverified-diploma-data+json",
"importance": "SKIPPABLE",
"fields": [],
"results": [],
"configuration": null,
"completedAsExpected": false
},
{
"checkIdentifier": "diploma-data",
"status": "SUCCESS",
"contentType": "application/vnd.otentica.singlecheck.check.diploma-data+json",
"importance": "SKIPPABLE",
"fields": [],
"results": [
{
"group": null,
"checkName": "diploma-data",
"fieldName": "diplomas",
"value": [
{
"type": "DIPLOMA",
"validFrom": null,
"validUntil": null,
"title": null,
"description": "IT Bachelor",
"issuer": "Hogeschool Utrecht",
"level": "0",
"verifiedOn": "2023-12-18T15:56:52.373384392Z",
"verifier": "duo",
"verificationStatus": null,
"fileId": "9caf3ebd-460b-4583-9823-1e9309289192"
}
],
"verified": true,
"verifiedBy": "emrex",
"verifiedOn": "2023-12-18T15:56:52.402431Z"
}
],
"configuration": {
"onlySkippableIfUnverifiedDiplomaAdded":true
},
"completedAsExpected": true
},
{
"checkIdentifier": "bank-data",
"status": "SUCCESS",
"contentType": "application/vnd.otentica.singlecheck.check.bank-data+json",
"importance": "SKIPPABLE",
"fields": [],
"results": [
{
"group": null,
"checkName": "bank-data",
"fieldName": "personalIbanCountry",
"value": "NL",
"verified": true,
"verifiedBy": "otentica",
"verifiedOn": "2023-12-18T15:57:28.011815Z"
},
{
"group": null,
"checkName": "bank-data",
"fieldName": "personalIban",
"value": "NL00RABO123455677",
"verified": true,
"verifiedBy": "otentica",
"verifiedOn": "2023-12-18T15:57:27.995607Z"
},
{
"group": null,
"checkName": "bank-data",
"fieldName": "personalIbanName",
"value": "T Kees",
"verified": true,
"verifiedBy": "otentica",
"verifiedOn": "2023-12-18T15:57:28.003952Z"
}
],
"configuration": {
"checkPersonalAccount": true,
"checkBusinessAccount": false
},
"completedAsExpected": true
}
],
"results": [
{
"group": null,
"checkName": "bank-data",
"fieldName": "personalIbanCountry",
"value": "NL",
"verified": true,
"verifiedBy": "otentica",
"verifiedOn": "2023-12-18T15:57:28.011815Z"
},
{
"group": null,
"checkName": "bank-data",
"fieldName": "personalIban",
"value": "NL00RABO123455677",
"verified": true,
"verifiedBy": "otentica",
"verifiedOn": "2023-12-18T15:57:27.995607Z"
},
{
"group": null,
"checkName": "identity-data",
"fieldName": "lastName",
"value": "Kees",
"verified": true,
"verifiedBy": "idin",
"verifiedOn": "2023-12-18T15:50:43.713299Z"
},
[...]
],
"style": {
"logoUrl": "https://app.otentica.nl/logos/logo.png",
"primaryColor": "#000000",
"secondaryColor": "#111111",
"primaryButtonColor": "#222222"
},
"expiryDate": "2024-02-01",
"anonymized": false,
"anonymizedOn": null,
"readyForProfessional": true,
"termsAndConditionsAccepted": true,
"logoutRedirectUrl": null,
"personalizedMessage": "<p>Welcome to our screening process.</p>",
"language": "DUTCH"
}
Important Fields
Take note of the following interesting fields:
Check Status Values
Status as part of a check:
PENDING: The check is not done yetSUCCESS: The check is completed (but not necessarily a successful validation)SKIPPED: The check is skippedFAILED: The check failed. This rarely happens, and usually occurs when there is a third party errorASYNC_WAITING: The check is waiting for an external party (for example the referents)
completedAsExpected
This indicates if the check was completed as expected. If this is false, manual investigation is advised. If this is true, the data was verified (except for some checks that do not validate data such as the unverified certificates check).
Data Retrieval Best Practices
We advise to retrieve the data from the results field. Sometimes data is retrieved using different checks to maximize the data availability. Also note that not all properties may return a value. For example using iDIN, some banks only return the initials of the name and not the complete first names. We try to retrieve as much data as possible, but we are dependent on the data provided by the third parties.
After the expiry date of the screening, the data is anonymized and thus no longer available.
Response Fields
Top-Level Fields
| Field | Type | Description |
|---|---|---|
id | integer | Flow ID |
dateCreated | datetime | When the flow was created |
dateModified | datetime | Last modification time |
status | string | Current flow status |
finished | boolean | Whether all checks are complete |
canFinish | boolean | Whether the flow can be marked as finished |
checks | array | List of all checks and their results |
results | array | Aggregated results from all checks |
expiryDate | date | When the screening data will be anonymized |
anonymized | boolean | Whether data has been anonymized |
anonymizedOn | datetime | When the data was anonymized (null if not yet) |
readyForProfessional | boolean | Whether the flow is ready for the professional |
termsAndConditionsAccepted | boolean | Whether the professional accepted the T&C |
nextCheckType | string/null | Next check awaiting action (null if none) |
language | string | Language of the flow (DUTCH or ENGLISH) |
logoutRedirectUrl | string/null | URL to redirect after logout (if configured) |
personalizedMessage | string/null | Custom HTML message shown to the professional |
customQuestions | array | The screening's custom questions (see below) |
customQuestionsAnswered | boolean | Whether the custom questions have been answered |
customQuestionsAnalysisPending | boolean | Whether document analysis on an answer is still running |
customQuestionsCompletedAsExpected | boolean/null | Whether the answers were as expected (null when not applicable) |
Custom Question Object
Each entry in customQuestions describes one question shown to the candidate. Questions are configured on a template and reach a screening through templateId.
| Field | Type | Description |
|---|---|---|
id | integer | The ID referenced by customQuestionId in the webhook payload |
label | string | The question text as the candidate sees it |
helpText | string/null | Optional explanation shown below the question |
questionType | string | OPEN_TEXT / YES_NO / SINGLE_CHOICE / MULTI_CHOICE / DATE / CONSENT |
options | string/null | JSON array of choices, as a string. null for OPEN_TEXT and YES_NO |
required | boolean | Whether an answer is mandatory |
sortOrder | integer | Display order |
templateId | integer/null | The template this question came from |
parentQuestionId | integer/null | null for a top-level question; the parent's id for a sub-question |
triggerValue | string/null | The parent answer that reveals this sub-question, e.g. "yes". Only set on a sub-question |
validityMonths | integer/null | How many months a DATE answer stays valid. This is what validUntil and validityStatus in the webhook are derived from |
expectedAnswer | string/null | The answer considered acceptable, where configured |
documentUploadEnabled | boolean | Whether a supporting document can be uploaded |
documentUploadRequired | boolean | Whether that upload is mandatory |
documentUploadPrompt | string/null | Prompt shown for the upload |
documentUploadTriggerMode | string | SPECIFIC_ANSWER or ANY_ANSWER |
documentUploadTriggerValue | string/null | The answer that triggers the upload request |
documentAiAnalysisEnabled | boolean | Whether the uploaded document is read automatically |
verifyAnswerWithDocument | boolean | Whether the document is checked against the answer, producing verdict in the webhook |
In customQuestions a sub-question is a sibling of its parent, identified by parentQuestionId. In the webhook's customAnswers the corresponding answer is nested under its parent in subAnswers. Match the two by customQuestions[].id against customAnswers[].customQuestionId, and store label + questionType as your stable key — see Custom Question Answers.
Check Object
Each check in the checks array contains:
| Field | Type | Description |
|---|---|---|
checkIdentifier | string | Check type identifier |
status | string | Check status |
contentType | string | Check content type |
importance | string | SKIPPABLE or NOT_SKIPPABLE |
fields | array | Input fields (if not completed) |
results | array | Verified data (if completed) |
completedAsExpected | boolean | Whether check completed successfully |
configuration | object | Check-specific configuration |
Result Object
Each result in the results array contains:
| Field | Type | Description |
|---|---|---|
group | string/null | Result grouping (if applicable) |
checkName | string | Check identifier |
fieldName | string | Field identifier |
value | any | The actual value |
verified | boolean | Whether the value is verified |
verifiedBy | string | Verification method (e.g., "idin", "duo", "emrex") |
verifiedOn | datetime | When verification occurred |
Polling for Updates
If you're not using webhooks, poll this endpoint periodically:
async function pollFlowStatus(token) {
const response = await fetch(
`https://api.otentica.nl/validation-flow/check/${token}`,
{
headers: {
Authorization: `Bearer ${API_KEY}`,
"Authorization-Method": "api-token",
Accept: "application/json",
},
}
);
const data = await response.json();
if (data.finished) {
// Process completed flow
processResults(data.checks);
} else {
// Check again later
setTimeout(() => pollFlowStatus(token), 30000); // Poll every 30 seconds
}
}
Extracting Verified Data
Filter and extract only verified data from the response:
function getVerifiedData(flowData) {
const verifiedData = {};
// Process data from the aggregated results field
flowData.results.forEach((result) => {
if (result.verified) {
verifiedData[result.fieldName] = {
value: result.value,
verifiedBy: result.verifiedBy,
verifiedOn: result.verifiedOn,
checkName: result.checkName,
};
}
});
return verifiedData;
}
// Example usage
const flowData = await getFlowStatus(checkToken);
const verifiedData = getVerifiedData(flowData);
console.log("Verified data:", verifiedData);
Monitoring Check Progress
Track individual check completion:
function getCheckProgress(flowData) {
const total = flowData.checks.length;
const completed = flowData.checks.filter((check) =>
["SUCCESS", "FAILED", "SKIPPED"].includes(check.status)
).length;
return {
total,
completed,
percentage: Math.round((completed / total) * 100),
status: flowData.status,
};
}