Quickstart — headless verification

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. 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,
    "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.

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://blaaiz-compliance-prod-storage.s3.eu-west-1.amazonaws.com/merchant-kyc/9d4c4ec5/9f2c7b41/4a7f1c92e0_passport%20front.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&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,
    "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,
    "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 no longer expires, and it accepts no more documents.

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"
  }
}

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.

On this page