Unstructured document verification
End-to-end integration guide for IBAN Verification and Proof of Address steps
This guide covers integrating flows that include an IBAN_VERIFICATION:v1 or PROOF_OF_ADDRESS:v1 step. Both are headless steps — your application collects the document image and submits it to IDnow via API. IDnow performs verification and extracts the relevant data block.
Overview
| Step | Input | Output (verified route) |
|---|---|---|
IBAN_VERIFICATION:v1 | DocumentImages | FundingSource, Verification |
PROOF_OF_ADDRESS:v1 | DocumentImages | ResidentialAddress, Verification |
Both steps support an optional compareIdentity flag. When enabled, a BasicIdentity data block from a prior step is used to cross-check the document holder's identity.
Step 1 — Upload the document image to the Vault
Before creating a session, upload the document image using the Vault upload endpoint. This returns a Vault reference ({ "$ref": "vault", "$id": "<uuid>" }) that you include in the session payload.
curl --request POST https://localhost:3000/api/v1/vault/files \
--header "Authorization: Bearer YOUR_API_KEY" \
--header "Content-Type: image/jpeg" \
--data-binary @document-front.jpg
{
"$ref": "vault",
"$id": "4d293362-9738-4a37-8d74-eb3acbc35749"
}
Repeat for the back side if required by your document type.
Step 2 — Create a session
Pass the Vault reference(s) as documentImages in the session input.
curl --request POST https://localhost:3000/api/v1/flows/{flowId}/live/sessions \
--header "Authorization: Bearer YOUR_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"input": {
"documentImages": {
"documentType": "IBAN",
"frontSide": { "$ref": "vault", "$id": "4d293362-9738-4a37-8d74-eb3acbc35749" }
}
},
"metadata": {
"subjectId": "your-internal-subject-id",
"locale": "en"
}
}'
For a PROOF_OF_ADDRESS:v1 step, use "documentType": "PROOF_OF_ADDRESS". For flows with compareIdentity: true, also include basicIdentity in the input:
{
"input": {
"documentImages": {
"documentType": "PROOF_OF_ADDRESS",
"frontSide": { "$ref": "vault", "$id": "4d293362-9738-4a37-8d74-eb3acbc35749" }
},
"basicIdentity": {
"familyName": "Schmidt",
"givenName": "Max",
"birthDate": "1985-08-22"
}
}
}
Step 3 — Poll for session completion
Poll the session endpoint until sessionStatus is COMPLETED, ERROR, ABORTED, or EXPIRED.
curl https://localhost:3000/api/v1/live/sessions/{sessionId} \
--header "Authorization: Bearer YOUR_API_KEY"
Verification typically completes within a few seconds. Use an exponential back-off with a reasonable timeout (e.g., poll every 2 s for the first 30 s, then every 10 s up to 5 minutes).
Step 4 — Read the results
When sessionStatus is COMPLETED, the results field in the session response contains references to the output data blocks.
IBAN Verification — verified session:
{
"sessionId": "0197c55f-5af6-7e3d-af9b-f2359b104be8",
"sessionStatus": "COMPLETED",
"outcome": "accepted",
"results": {
"fundingSource": {
"type": "dataBlock",
"id": "c3a1e2f4-1234-4b6a-a9d8-123456789abc"
},
"verification": {
"type": "dataBlock",
"id": "d4b2f3a1-5678-4c7b-b0e9-abcdef012345"
}
}
}
Proof of Address — verified session:
{
"sessionId": "0197c55f-5af6-7e3d-af9b-f2359b104be9",
"sessionStatus": "COMPLETED",
"outcome": "accepted",
"results": {
"residentialAddress": {
"type": "dataBlock",
"id": "e5c3d4b2-9012-4e8c-c1f0-bcdef0123456"
},
"verification": {
"type": "dataBlock",
"id": "f6d4e5c3-0123-4f9d-d2g1-cdef01234567"
}
}
}
On non-verified routes (not_verified, fraud_detected), only the verification data block is present in results. If analysis could not complete at all, the session ends with sessionStatus: "ERROR" instead of routing to a completed outcome — see Handling non-verified outcomes.
Step 5 — Fetch the data block content
Use the id from results to retrieve the full data block content.
curl https://localhost:3000/api/v1/live/sessions/{sessionId}/datablocks \
--header "Authorization: Bearer YOUR_API_KEY"
FundingSource data block:
{
"datablockId": "c3a1e2f4-1234-4b6a-a9d8-123456789abc",
"type": "fundingSource",
"createdBy": { "stepId": "IBAN_CHECK", "clock": 1 },
"createdAt": "2026-01-15T10:30:00.000Z",
"status": "STORED",
"content": {
"iban": "FR7630006000011234567890189",
"bic": "BNPAFRPP",
"ibanCountry": "FR",
"accountNumber": "1234567890",
"bankCode": "30006",
"branchCode": "00001",
"bankName": "BNP Paribas",
"bankAddressLines": ["75009 Paris"],
"accountKey": "89"
}
}
ResidentialAddress data block:
{
"datablockId": "e5c3d4b2-9012-4e8c-c1f0-bcdef0123456",
"type": "residentialAddress",
"createdBy": { "stepId": "POA_CHECK", "clock": 1 },
"createdAt": "2026-01-15T10:30:00.000Z",
"status": "STORED",
"content": {
"residentAddress": "12 Rue de la Paix, 75002 Paris, France",
"residentStreet": "Rue de la Paix",
"residentHouseNumber": "12",
"residentCity": "Paris",
"residentPostalCode": "75002",
"residentCountry": "FR",
"residentState": null
}
}
For the full attribute reference, see FundingSource and ResidentialAddress.
Handling non-verified outcomes
When the step routes to not_verified or fraud_detected, only the Verification data block is produced. Inspect its status field to determine the reason.
Verification.status | Meaning |
|---|---|
verified | Document passed all checks |
rejected | Document failed validation or did not match expected data |
fraudDetected | Suspicious document or identity — treat as a hard stop |
If the underlying analysis could not complete at all (e.g. no verdict could be determined), no route is taken — the session instead terminates with sessionStatus: "ERROR" and no Verification data block is produced. Treat this as a processing fault to retry or escalate, not as a completed outcome to branch on.
{
"datablockId": "d4b2f3a1-5678-4c7b-b0e9-abcdef012345",
"type": "verification",
"content": {
"status": "rejected",
"terminationReason": {
"code": "document_rejected",
"message": "Document could not be validated"
},
"methods": [],
"provider": "IDnow",
"trustFramework": null,
"assuranceLevel": null,
"verifiedAt": "2026-01-15T10:30:01.000Z",
"verificationProcessId": "file-uid-from-provider"
}
}