Headless verification quickstart

Upload identity documents and proof of address from your own server, then submit the session for review.

This quickstart uses the HEADLESS fulfilment mode. You already hold the document images, so you upload them from your server. Blaaiz never contacts the person you verify.

A headless session asks for DOCUMENTS, PROOF_OF_ADDRESS, or both. You can add AML_SCREENING to the set, or ask for AML_SCREENING alone. See AML screening. If you need a live selfie, use the hosted quickstart instead. Read Signa overview first for the session model.

Before you start

  • Signa must be enabled for your business.
  • Your OAuth credentials must carry the compliance-kyc:create and compliance-kyc:read scopes.
  • Your kyc_url webhook must be registered. See Signa webhooks.

The examples use the production base URL https://api-prod.blaaiz.com. For development, use https://api-dev.blaaiz.com.

Flow

Step 1: Create the session

curl -X POST https://api-prod.blaaiz.com/api/external/compliance/kyc/sessions \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_reference": "user_10482",
    "idempotency_key": "kyc-user_10482-2026-08-29",
    "requirements": ["DOCUMENTS"],
    "applicant": {
      "first_name": "Amara",
      "last_name": "Okafor",
      "dob": "1993-04-17",
      "country": "NGA"
    }
  }'

A ["DOCUMENTS"] set supports HEADLESS only, so you can omit fulfilment_mode. Send "fulfilment_mode": "HEADLESS" if you prefer to be explicit.

{
  "message": "Verification session created successfully.",
  "data": {
    "id": "9f2c7b41-6d3e-4c8a-9a20-1e6f0b5d7c33",
    "business_id": "9d4c4ec5-572d-49de-a362-f01ed09f2b1b",
    "customer_reference": "user_10482",
    "requirements": ["DOCUMENTS"],
    "fulfilment_mode": "HEADLESS",
    "status": "AWAITING_INPUT",
    "steps": {
      "documents": "PENDING",
      "selfie": null,
      "proof_of_address": null
    },
    "rejection": null,
    "aml_screening": null,
    "redirect_url": null,
    "expires_at": "2026-08-30T09:14:22.000Z",
    "completed_at": null,
    "created_at": "2026-08-29T09:14:22.000Z",
    "verification_link": null,
    "link_expires_at": null
  }
}

steps.selfie is null because the requirement set has no SELFIE. aml_screening is null because the set has no AML_SCREENING.

The session must be AWAITING_INPUT before it accepts a document. If status is CREATED, repeat the create call with the same idempotency_key.

Step 2: Get an upload URL

Do not send document bytes in the request body. A request body larger than about 8 KB is rejected at the network edge, before the API sees it. Use the upload URL for every real document.

curl -X POST https://api-prod.blaaiz.com/api/external/compliance/kyc/sessions/9f2c7b41-6d3e-4c8a-9a20-1e6f0b5d7c33/documents/upload-url \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "file_name": "passport front.jpg",
    "id_doc_type": "PASSPORT"
  }'
{
  "message": "Verification session document upload URL generated successfully.",
  "data": {
    "url": "https://storage.example.com/uploads/4a7f1c92e0_passport%20front.jpg?X-Amz-Expires=300&X-Amz-Signature=EXAMPLE",
    "file_name": "4a7f1c92e0_passport front.jpg",
    "headers": {}
  }
}
FieldDescription
urlThe presigned URL you upload the bytes to. It is valid for 5 minutes.
file_nameThe name Blaaiz gave the staged file. Send this exact value in step 4.
headersHeaders you must send with the upload. The object can be empty.

Your file_name must use letters, numbers, spaces, dashes, or underscores, and must end in .jpg, .jpeg, .png, .webp, or .pdf. Blaaiz adds a random prefix and returns the result in data.file_name.

Step 3: Upload the bytes

Send the file to the url with an HTTP PUT. Add every header from the headers object.

curl -X PUT "PRESIGNED_URL_FROM_STEP_2" \
  --upload-file ./passport-front.jpg

A staged file must be 5 MB or smaller. Blaaiz rejects a larger file when you register it in step 4.

Step 4: Register the document

This call tells Blaaiz which staged file belongs to the session, and what the file is.

curl -X POST https://api-prod.blaaiz.com/api/external/compliance/kyc/sessions/9f2c7b41-6d3e-4c8a-9a20-1e6f0b5d7c33/documents \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "passport-front.jpg",
    "content_type": "image/jpeg",
    "id_doc_type": "PASSPORT",
    "country": "NGA",
    "file_name": "4a7f1c92e0_passport front.jpg"
  }'
FieldDescription
filenameThe name Blaaiz stores the document under.
content_typeOne of image/jpeg, image/png, image/webp, application/pdf.
id_doc_typeOne of PASSPORT, ID_CARD, DRIVERS, RESIDENCE_PERMIT, UTILITY_BILL, BANK_STATEMENT, SELFIE. UTILITY_BILL and BANK_STATEMENT satisfy the proof-of-address step. Every other type satisfies the documents step.
countryThe country of the document, as an ISO 3166-1 alpha-3 code.
file_nameThe data.file_name value from step 2.

The response is the session, with steps.documents set to SUBMITTED:

{
  "message": "Verification session document uploaded successfully.",
  "data": {
    "id": "9f2c7b41-6d3e-4c8a-9a20-1e6f0b5d7c33",
    "business_id": "9d4c4ec5-572d-49de-a362-f01ed09f2b1b",
    "customer_reference": "user_10482",
    "requirements": ["DOCUMENTS"],
    "fulfilment_mode": "HEADLESS",
    "status": "AWAITING_INPUT",
    "steps": {
      "documents": "SUBMITTED",
      "selfie": null,
      "proof_of_address": null
    },
    "rejection": null,
    "aml_screening": null,
    "redirect_url": null,
    "expires_at": "2026-08-30T09:14:22.000Z",
    "completed_at": null,
    "created_at": "2026-08-29T09:14:22.000Z"
  }
}

To send more than one document, repeat steps 2 to 4 for each file. Give each file its own id_doc_type.

Proof of address

When the requirement set includes PROOF_OF_ADDRESS, upload the proof of address the same way. Repeat steps 2 to 4, and set id_doc_type to the type of document you hold.

id_doc_typeThe document
UTILITY_BILLA utility bill.
BANK_STATEMENTA bank statement.

A photo and a PDF are both accepted, so send image/jpeg, image/png, image/webp, or application/pdf.

{
  "filename": "bank-statement.pdf",
  "content_type": "application/pdf",
  "id_doc_type": "BANK_STATEMENT",
  "country": "NGA",
  "file_name": "b1e8d3f704_bank statement.pdf"
}

On a proof of address, country means the country of the address on the document. It is not the nationality of the person, and it is not the country of the identity document. A person can hold a passport of one country and a bank statement of another.

The upload sets steps.proof_of_address to SUBMITTED:

{
  "steps": {
    "documents": "SUBMITTED",
    "selfie": null,
    "proof_of_address": "SUBMITTED"
  }
}

steps.documents is null on a ["PROOF_OF_ADDRESS"] session, because that set asks for no identity document. Upload the proof of address only, then submit.

Small files

A document under about 8 KB can travel in the request body. Replace file_name with content_base64 and send the base64 of the file. Send one of the two fields, never both.

{
  "filename": "passport-front.jpg",
  "content_type": "image/jpeg",
  "id_doc_type": "PASSPORT",
  "country": "NGA",
  "content_base64": "/9j/4AAQSkZJRgABAQAAAQABAAD..."
}

Real identity documents are larger than the edge limit. Use the upload URL for them.

Step 5: Submit the session for review

curl -X POST https://api-prod.blaaiz.com/api/external/compliance/kyc/sessions/9f2c7b41-6d3e-4c8a-9a20-1e6f0b5d7c33/submit \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

The session moves to IN_REVIEW:

{
  "message": "Verification session submitted successfully.",
  "data": {
    "id": "9f2c7b41-6d3e-4c8a-9a20-1e6f0b5d7c33",
    "business_id": "9d4c4ec5-572d-49de-a362-f01ed09f2b1b",
    "customer_reference": "user_10482",
    "requirements": ["DOCUMENTS"],
    "fulfilment_mode": "HEADLESS",
    "status": "IN_REVIEW",
    "steps": {
      "documents": "SUBMITTED",
      "selfie": null,
      "proof_of_address": null
    },
    "rejection": null,
    "aml_screening": null,
    "redirect_url": null,
    "expires_at": "2026-08-30T09:14:22.000Z",
    "completed_at": null,
    "created_at": "2026-08-29T09:14:22.000Z"
  }
}

Blaaiz refuses the submit in three cases:

  • A step of the set is still PENDING. Upload what that step asks for first. A null step never blocks the submit.
  • The session already left AWAITING_INPUT. You cannot submit twice.
  • The session is HOSTED. The person completes that session on the verification link.

Review starts only when every step of the set is SUBMITTED. On a ["DOCUMENTS", "PROOF_OF_ADDRESS"] session, that means both uploads must land before you call submit.

An IN_REVIEW session accepts no more documents, and it no longer expires at expires_at. With AML_SCREENING in the set, the session expires if the screening gives no result in the 72 hours after the screening starts.

Step 6: Wait for the webhook

{
  "event": "merchant.kyc.session.completed",
  "data": {
    "session_id": "9f2c7b41-6d3e-4c8a-9a20-1e6f0b5d7c33",
    "business_id": "9d4c4ec5-572d-49de-a362-f01ed09f2b1b",
    "customer_reference": "user_10482",
    "requirements": ["DOCUMENTS"],
    "result": "REJECTED",
    "rejection_reason": "The document image is too blurred to read.",
    "rejection_type": "RETRYABLE",
    "completed_at": "2026-08-29T09:48:11.000Z",
    "aml_screening": null
  }
}

A RETRYABLE rejection means the person can try again. Create a new session with a new idempotency_key and upload clearer images. A FINAL rejection means you must not retry.

Verify the x-blaaiz-signature header before you trust the payload. See Signa webhooks.

AML screening

AML_SCREENING checks the person against sanctions, politically exposed persons (PEP), adverse media, and criminal lists. It has no step, so you upload nothing for it. See AML screening for the rules.

Add AML screening to a document set

To screen the person on the document, add AML_SCREENING to the set, for example ["DOCUMENTS", "AML_SCREENING"]. Then do steps 1 to 6 as before.

After the review approves the document, the session stays IN_REVIEW. Blaaiz screens the verified name and date of birth from the document. The webhook arrives after the screening gives its result.

AML screening only

Ask for AML_SCREENING alone to screen a person without a document. The session has nothing to collect, so you upload nothing and you do not submit it.

A set without DOCUMENTS needs all four applicant fields, because Blaaiz screens these details:

curl -X POST https://api-prod.blaaiz.com/api/external/compliance/kyc/sessions \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_reference": "user_10652",
    "idempotency_key": "kyc-user_10652-2026-10-10",
    "requirements": ["AML_SCREENING"],
    "fulfilment_mode": "HEADLESS",
    "applicant": {
      "first_name": "Amara",
      "last_name": "Okafor",
      "dob": "1993-04-17",
      "country": "NGA"
    }
  }'

The session is IN_REVIEW at once, and the screening starts:

{
  "message": "Verification session created successfully.",
  "data": {
    "id": "c7e04a19-58b2-4f6d-9e13-2a8d6f0b4c75",
    "business_id": "9d4c4ec5-572d-49de-a362-f01ed09f2b1b",
    "customer_reference": "user_10652",
    "requirements": ["AML_SCREENING"],
    "fulfilment_mode": "HEADLESS",
    "status": "IN_REVIEW",
    "steps": {
      "documents": null,
      "selfie": null,
      "proof_of_address": null
    },
    "rejection": null,
    "aml_screening": {
      "status": "PENDING",
      "match_types": []
    },
    "redirect_url": null,
    "expires_at": "2026-10-11T08:20:05.000Z",
    "completed_at": null,
    "created_at": "2026-10-10T08:20:05.000Z",
    "verification_link": null,
    "link_expires_at": null
  }
}

If one of the four applicant fields is missing, the create call fails with 422:

AML_SCREENING without DOCUMENTS needs applicant.first_name, applicant.last_name, applicant.dob and applicant.country.

When the screening gives its result, Blaaiz posts merchant.kyc.session.completed to your kyc_url:

{
  "event": "merchant.kyc.session.completed",
  "data": {
    "session_id": "c7e04a19-58b2-4f6d-9e13-2a8d6f0b4c75",
    "business_id": "9d4c4ec5-572d-49de-a362-f01ed09f2b1b",
    "customer_reference": "user_10652",
    "requirements": ["AML_SCREENING"],
    "result": "APPROVED",
    "rejection_reason": null,
    "rejection_type": null,
    "completed_at": "2026-10-10T08:20:41.000Z",
    "aml_screening": {
      "status": "CLEAR",
      "match_types": []
    }
  }
}
  • If the screening finds a match, result is REJECTED and rejection_type is FINAL. aml_screening.match_types names the kinds of list.
  • If the screening gives no result in 72 hours, Blaaiz sends merchant.kyc.session.expired instead.
  • If you do not use the webhook, poll GET a session and read aml_screening.

Do not show match_types to the person you verify. A warning to a person under suspicion is tipping off. Show the person rejection_reason only.

A session of AML_SCREENING alone reads no document. The session data endpoints return no applicant details and no images for it.

Read the session data

After the verdict, you can read the applicant details that Blaaiz read from the documents, and download the images. See Read session data.

On this page