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. sideis required forGOVERNMENT_IDand accepted for any document that has two sides; omit it forSELFIE,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.extractedfields 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.
Authentication
Authenticate every Certivant API call with a Bearer API key — covers key creation, scopes, the Authorization header format, zero-downtime rotation, revocation, and security best practices.
Forms
List a tenant's KYC and KYB verification forms via the Certivant REST API — discover which form to run a verification against, filter by type, and read each form's slug, status, and embeddable flag.