Account verification before creating payins
Authenticated collection requireskyc_status: approved for person accounts and approved KYB for companies; until then, POST /v1/payins (QR/card/checkout/bank_transfer/fintoc), collect OTP/collect, dedicated deposit accounts, stored-card charges and subscriptions return 403 verification_required. Existing public links, passive deposits, org-admin assignment and system-created instruments remain available; request POST /v1/me/verification/link, then retry after approval.
Identity verification proves a person (KYC) or company (KYB) is who
they claim to be, with real evidence: a complete form, document uploads
validated by OCR and a video liveness check. It has two sides:
- Your own verification (onboarding) — mandatory: until approved, your account can only fund (payins, crypto deposits, incoming transfers) and read. Person ⇒ KYC; company ⇒ KYB.
- Verifying your customers (company accounts only) — generate hosted links or send data through the API to verify your own end customers, with a fixed fee per verification.
Your own verification (onboarding)
When you register, your account starts unverified (kyc_status: none) and can only fund and read. Any outgoing-money action (payouts, transfers,
withdrawals, banking, cards) answers 403 verification_required until you
are approved.
Request your verification link
201 response (if you already have an open link, the same one is returned
with 200):kind derives from your account type: person ⇒ kyc, company ⇒
kyb. Onboarding is free for you.Complete the wizard
url: the hosted wizard guides you through the form, document
uploads (identity, proof of residence; corporate documents for companies)
and — for KYC — the camera liveness check.Wait for review
submission fields are returned when available:
pending_documents, changes_requested_comments, documents_received and
documents_gate; rejection_reason is intentionally not exposed and a
temporary core refresh failure may omit them.When compliance approves, your kyc_status becomes approved
automatically, every service unlocks, and you receive the
kyc_verification_status_changed webhook with self_onboarding: true.decision_source field of the status webhook ("auto" / "admin") tells you who decided.display_name (person = first + last name; company = legal
name), tax_id and country are taken from the verification and from then
on are immutable via PATCH /v1/me (409 identity_locked) — the
verified identity is the source of truth.kyc_status: rejected), contact your operator about a new link.Verifying your customers (company accounts only)
A verified company account can verify its own end customers. Each created verification bills the configured fixed fee (kyc_verification /
kyb_verification; 0 = free), automatically refunded if creation
fails. Person accounts receive 403 company_account_required.
Option A — Hosted links (recommended)
Your customer completes EVERYTHING in the white-label wizard: form, documents and liveness. You only generate the link and wait for the webhook.external_customer_id(required): YOUR reference for the verified customer — echoed back on every webhook and query. Values equal toselfor ending in:selfare reserved for account onboarding and are rejected with400 invalid_payload.idempotency_key(required): a retry with the same key returns the original link and never double-charges.country(KYB only):us,cl,ve,br,mx,co,pe,bo,py,arorgeneric(withgeneric_countryISO alpha-2, e.g."ES"). Individual KYC takes no country.expires_in_days(optional, 1–30): omitted, the link never expires.
201 response:
Option B — Data through the API
If you already hold the customer’s data, create the verification directly (no wizard). The submission enters the same review queue:- Countries in ISO alpha-3 (
CHL,USA,VEN…); datesYYYY-MM-DD;id_type:passport | id_card | drivers_license. - KYB: body
{ external_customer_id, country?, business: {…}, ubos?, directors?, signers?, bank_info?, metadata? }onPOST /v1/kyb/submissions. - No liveness is required at creation: the KYC submission carries
liveness_pending: true; close it with a liveness link. - Re-sending with the same
external_customer_idwhile the submission is open (pending_review,changes_requested,more_info_required) updates the same submission and does not charge again.
201 response:
pending_documents, rejection_reason,
changes_requested_comments, documents_received, documents_gate and
aml_decision for KYB.
Documents through the API
Documents are optional at creation (if missing, compliance will request them viamore_info_required). 3-step flow:
Presign
identity, proofOfResidence; KYB: legalPresence,
ownershipStructure, controlStructure, companyDetails. Types:
application/pdf, image/png, image/jpeg; 15 MB max; the upload URL
expires in 15 minutes.Upload
PUT the binary straight to upload_url with the same Content-Type.Confirm
kyc_document_validated / kyb_document_validated webhook and is
queryable with GET:outcome: MATCH, REVIEW (manual review), NO_MATCH. Each item also exposes:id: the validation identifier (used by the compliance team to review it).effective_outcome: the outcome that currently governs — the admin’s manual resolution from the admin panel if one exists, otherwise the OCR engine outcome. In the submission detail (GET /v1/kyc/submissions/{id}) thedocuments_gateblock summarizes whether ALL documents are already resolved (ok: true), withmatched/totaland theunresolvedlist of pending ones.manual_review: present only when an admin manually resolved the validation from the admin panel. The account view carriesoutcomeandreviewed_at(without the internal note or the reviewer).
effective_outcome and manual_review, plus the corresponding kyc_document_validated / kyb_document_validated webhook.Liveness check (liveness link)
KYC submissions created through the API are born withliveness_pending: true (the liveness check is a browser camera flow).
Generate a minimal hosted link for your customer to complete it:
- Free (the service was billed when the submission was created). If an
open link exists, the POST returns the same one; if the check already
passed,
400 liveness_already_completed. GET .../liveness_linkreturns the latest link and the current check state ({ "liveness": { "status", "outcome", "passed" } }).- On pass (outcome
PASSorREVIEW): the submission clearsliveness_pendingand thekyc_liveness_completedwebhook fires.
One verification for everything (reusable identity)
A customer’s approved verification is their single identity inside CBPay: you never re-type their data or re-upload their documents in any other product.- Third-party banking:
POST /v1/banking/third-partiesrequires theverification_idof an approved verification of the third party. The type (INDIVIDUAL/COMPANY) comes from the kind (KYC ⇒ person, KYB ⇒ company), the data (name, email, address) auto-fills from the verified profile, and the already-validated documents are re-delivered automatically to the banking provider (documents_syncedin the response). Details in Banking. - Cards for designated persons:
POST /v1/cardswith a personcardholderrequirescardholder.verification_idof that person’s approved KYC. The cardholder’s identity and documents come from the verification; you only add the issuer-specific fields (occupation,salary_usd). Details in Cards. - Your own account: your approved onboarding is reused too — when creating your banking customer or your first card, missing data and documents auto-fill from your verification.
Compliance report (KYB only)
For every KYB verification you can download the signed compliance report (PDF, evidence for your own auditors):Verification report (PDF + JSON)
Besides the processor’s report, every decided KYC or KYB submission has its verification report generated by the platform. It is the full file, not a summary: verified identity (person or company), declared economic profile, risk attestations, masked bank account, decision lifecycle, documents with their document validation, liveness check, related parties with their own screening (KYB) and the AML screening with the detail of every match — all with an integrity hash and a public verification code. Two formats (?format=pdf|json, default pdf) and three languages (?lang=en|es|zh,
default en). It is free: it is a read of a verification you already paid
for.
Report sections:
gate
session is the check that unlocked onboarding — it usually only carries the
selfie. A media_recapture session is a later evidence capture and is the
one that carries the full package (selfie + one frame per requested gesture- video). A
media_recapturethat endedoutcome: "FAIL"still matters — it can be the only session with usable video — so iterate the wholeliveness[]array instead of readingliveness[0]; the subject’s current verdict is always thegatesession’soutcome. In a KYB,parties[].liveness(singular) stays for compatibility and always points to that party’sgatesession, whileparties[].liveness_sessions[]carries every session for that party.
http and https links are embedded; any other scheme is dropped and
the text stays unlinked.For your third parties (company accounts)
format=json response (shape summary — the PDF renders from the same model):
"partial": ["aml_unavailable"] — the section is never fabricated.Related parties and their screening (KYB)
In a company verification, every UBO, control person and signer in the file comes out as aparties[] entry with their full identity, their ownership,
the documents and liveness check that belong to them, and their own AML
screening with continuous monitoring enabled. The (source, index) pair is
the party’s stable identity inside the file: it is what links their documents
(uboIdentity:0) and what makes their screening always the same, no matter
how many times you download the report.
Screening the parties is free (it is a due-diligence duty, not a billable
product) and stays monitored: if a UBO lands on a sanctions list after
onboarding, the alert shows up on its own.
"partial": ["party_aml_unavailable"] and the missing
screening runs in the background: the next download already carries it.For your own onboarding
aml_detail: false): you see the status per category — sanctions, pep
and adverse_media as clear or under_review — without the match details.
The same applies to the screening of your related parties: their AML sections
are aggregated too. The rest of the file (identity, economic profile,
documents, liveness, parties) is complete.
Public report verification
Every report carries averification_code (printed on the PDF next to a QR).
Anyone can confirm its authenticity without credentials:
Webhooks
kyc_verification_status_changed):
"self_onboarding": true instead of
external_customer_id. Subscribe like every other event (see
Webhooks).
Costs (configured by your operator, can be 0)
Errors
FAQ
Why can't I create payouts right after registering?
Why can't I create payouts right after registering?
POST /v1/me/verification/link and complete it — approval unlocks everything.Why does verification ask me to contact an administrator?
Why does verification ask me to contact an administrator?
POST /v1/me/verification/link does not re-serve or create a self-service link. The administrator must restore the account or reopen verification first.Hosted links vs data through the API — which one?
Hosted links vs data through the API — which one?
When is the fee billed and when not?
When is the fee billed and when not?
Why can't my person account create links?
Why can't my person account create links?
/v1/me/verification onboarding.Compliance asked for more documents — how do I send them?
Compliance asked for more documents — how do I send them?
more_info_required with pending_documents. Use this
page’s presign → upload → confirm flow; the submission then returns to review.Does this replace AML screening?
Does this replace AML screening?
Can I reuse a customer's verification in other products?
Can I reuse a customer's verification in other products?
submission_id as verification_id when registering a banking user or
issuing a card for a designated person; data and documents auto-fill. See
reusable identity.Do I get an email when my verification is approved or rejected?
Do I get an email when my verification is approved or rejected?
kyc_status_changed/kyb_status_changed webhook you integrated.