Getting startedYour first request

Your first request

End-to-end walkthrough — create a request, upload documents server-side, submit it, handle the webhook.

The Quickstart used a hosted session because it's the simplest path. This page walks through the alternative: collecting documents in your own UI and uploading them server-to-server. Choose this approach when you want full control over the applicant experience, or when you already have document images on file.

The lifecycle

stateDiagram-v2
  [*] --> PENDING_UPLOAD: POST /requests
  PENDING_UPLOAD --> PENDING_UPLOAD: POST /documents (each upload)
  PENDING_UPLOAD --> PROCESSING: POST /submit
  PROCESSING --> APPROVED: auto-decision: approve
  PROCESSING --> REJECTED: auto-decision: reject
  PROCESSING --> ESCALATED: auto-decision: escalate
  ESCALATED --> APPROVED: POST /decision (human)
  ESCALATED --> REJECTED: POST /decision (human)
  REJECTED --> PENDING_UPLOAD: POST /resubmit
  APPROVED --> [*]

Each state transition emits a webhook. You only need to act on PROCESSING, APPROVED, REJECTED, and ESCALATED in most integrations.

Step 1 — Create the request

curl -X POST https://app.certivant.com/api/v1/kyc/requests \
  -H "Authorization: Bearer ck_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "type": "INDIVIDUAL",
    "externalRef": "user-9281",
    "applicant": {
      "fullName": "Alex Singh",
      "dateOfBirth": "1988-11-03",
      "nationality": "GB",
      "email": "alex@example.com",
      "phone": "+447700900100",
      "emailVerified": true,
      "phoneVerified": false
    }
  }'

Response (201):

{
  "id": "req_01J0...",
  "status": "PENDING_UPLOAD",
  "pendingDocTypes": ["GOVERNMENT_ID", "SELFIE"],
  "applicant": { "fullName": "Alex Singh", ... },
  "createdAt": "..."
}

pendingDocTypes tells you which documents this verification still needs. It updates as you upload — once empty, the request is ready to submit.

Step 2 — Upload the documents

Each document upload is a separate multipart/form-data POST. Pass documentType (and side for two-sided documents like driver's licenses) as query parameters, and the file in a field named file.

# Front of an ID card
curl -X POST "https://app.certivant.com/api/v1/kyc/requests/req_01J0.../documents?documentType=GOVERNMENT_ID&side=front" \
  -H "Authorization: Bearer ck_live_..." \
  -F "file=@/path/to/id-front.jpg"

# Back of the same ID card
curl -X POST "https://app.certivant.com/api/v1/kyc/requests/req_01J0.../documents?documentType=GOVERNMENT_ID&side=back" \
  -H "Authorization: Bearer ck_live_..." \
  -F "file=@/path/to/id-back.jpg"

# Selfie
curl -X POST "https://app.certivant.com/api/v1/kyc/requests/req_01J0.../documents?documentType=SELFIE" \
  -H "Authorization: Bearer ck_live_..." \
  -F "file=@/path/to/selfie.jpg"

Constraints:

  • File size: 10 MB maximum per file.
  • MIME types: image/jpeg, image/png, image/webp, application/pdf.
  • side is required for GOVERNMENT_ID and accepted for any document that has two sides; omit it for SELFIE, PROOF_OF_ADDRESS, etc.

Each upload returns the updated request status:

{
  "id": "doc_01J1...",
  "documentType": "GOVERNMENT_ID",
  "side": "front",
  "status": "pending",
  "requestStatus": "PENDING_UPLOAD",
  "pendingDocTypes": ["SELFIE"],
  "fileSize": 412938,
  "mimeType": "image/jpeg"
}

When pendingDocTypes is empty, you're ready to submit.

Step 3 — Submit for processing

Verify email and phone first. Email and phone verification is part of the checks. If your organization requires it (KYC/B Settings), make sure each required channel is verified before you submit — either by setting emailVerified / phoneVerified: true on create (as in step 1, when you've already validated them yourself), or by having the applicant complete the email/phone code step in a hosted session. An unverified required channel fails the contact-verification check during processing.

curl -X POST https://app.certivant.com/api/v1/kyc/requests/req_01J0.../submit \
  -H "Authorization: Bearer ck_live_..."

Response:

{ "id": "req_01J0...", "status": "PROCESSING", "message": "..." }

The request moves into PROCESSING. Certivant's risk engine runs document authenticity checks, OCR, face match (if you uploaded a selfie), liveness, sanctions screening, and any custom rules your organization has configured. This typically completes within seconds.

Don't poll for the result. Set up a webhook (see step 4) — that's how Certivant tells you the request is done. Polling burns rate limit and adds latency.

Step 4 — Receive the webhook

Configure a webhook endpoint at KYC → Settings → Webhooks in the dashboard. Whenever the request transitions, Certivant POSTs to your URL:

{
  "event": "kyc.request.approved",
  "requestId": "req_01J0...",
  "organizationId": "org_01H...",
  "externalRef": "user-9281",
  "status": "APPROVED",
  "timestamp": "2026-05-27T10:04:21.000Z",
  "data": {
    "score": 94,
    "decision": "approve",
    "extracted": {
      "fullName": "Alex Singh",
      "dateOfBirth": "1988-11-03",
      "documentNumber": "GBR-1234567"
    },
    "liveness": { "isLive": true, "confidence": 0.98 },
    "faceMatch": { "isMatch": true, "confidence": 0.91 }
  }
}

Verify the X-Certivant-Signature-V2 header (see Signature verification), look up the row in your database by externalRef, and proceed.

Step 5 — Handle escalation and rejection

Not every request approves cleanly. The two outcomes you must plan for:

kyc.request.escalated

The automated engine wasn't confident — usually because the document was low-quality, the face match scored borderline, or a sanctions hit needs a human eye. A reviewer in your organization opens the request in the dashboard and decides. You don't have to do anything; the eventual decision fires another webhook (kyc.request.approved or kyc.request.rejected).

You can issue the decision programmatically with kyc:manage scope:

curl -X POST https://app.certivant.com/api/v1/kyc/requests/req_01J0.../decision \
  -H "Authorization: Bearer ck_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "decision": "APPROVED", "note": "Confirmed against internal CRM record" }'

kyc.request.rejected

The applicant failed checks — expired document, sanctions hit, liveness failed, etc. Look at data.factors and data.disqualifiers in the webhook payload to see why. You can let the applicant retry by resubmitting:

curl -X POST https://app.certivant.com/api/v1/kyc/requests/req_01J0.../resubmit \
  -H "Authorization: Bearer ck_live_..."

This resets the request to PENDING_UPLOAD and clears prior documents. The applicant can then re-upload (or use a fresh hosted session URL) and you can submit again.

What to build next

  • Hook the webhook handler into your background job queue so you can retry your own downstream work if it fails.
  • Store the data.extracted fields against your user record — name, DOB, document number — and avoid asking the user for them again.
  • Add an admin view in your own product that links to the Certivant dashboard for any request your team needs to inspect.

For the full endpoint reference and edge cases, see KYC requests.