KYC flow
How to drive the end customer's platform KYC, from user creation to approved status, with webhooks or polling.
New to KYC?
The Overview page covers both KYC tiers (platform and crypto), states, webhooks, and errors. This page is the practical guide for the platform KYC, which is what your integration drives.
Read more →Every BlendFi end customer must complete the platform KYC before any money moves. It's a regulatory requirement: without kyc_status: approved, any quote or transaction returns kyc_required (422). BlendFi conducts the verification behind the endpoint; your integration only talks to the BlendFi API.
How it all fits together
Your app BlendFi Identity verification
│ │ │
├── POST /v1/users ────►│ │
│◄── user created ──────┤ │
│ (kyc: not_started) │ │
│ │ │
├── POST /v1/users/{id}/kyc ─► │
│ ├── start submission ─────────►│
│ │◄────── signed link ──────────┤
│◄── submission created ┤ │
│ (kyc: pending, │ │
│ websdk_url) │ │
│ │ │
├── redirect end customer to websdk_url ───────────────►
│ │ (customer submits docs) │
│ │◄──── result webhook ─────────┤
│ │ │
│◄── webhook user.kyc_approved ─┤ │
│ (kyc: approved) │ │
│ │ │
│ ├── starts crypto KYC in background
│ │You only call BlendFi. BlendFi runs the verification and reflects results back via webhook.
Five outcomes
`not_started`
User exists; submission not started yet. Cannot transact.
`pending`
Submission in progress, under review. Cannot transact.
`approved`
End customer passed the platform KYC. Can transact. Default validity is 12 months; renewed before expiration.
`rejected`
Verification rejected the documents. End customer cannot transact. Can resubmit (returns to pending), except for terminal cases.
`expired`
Approved KYC exceeded validity. End customer must reverify before transacting.
The full path
1. Create the user
curl -X POST $BLENDFI_BASE/v1/users \
-H "Authorization: Bearer $BLENDFI_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "content-type: application/json" \
-d '{
"external_id": "your-user-001",
"name": "Ada Lovelace",
"cpf": "52998224725"
}'The user is created with kyc_status: not_started. The CPF is stored encrypted and never returned in plaintext.
2. Start the KYC submission
The call generates a one-time signed URL hosted by BlendFi for the end customer to complete the verification.
curl -X POST $BLENDFI_BASE/v1/users/01J.../kyc \
-H "Authorization: Bearer $BLENDFI_KEY" \
-H "Idempotency-Key: $(uuidgen)"Response:
{
"user_id": "01J...",
"status": "pending",
"websdk_url": "https://verify.blendfi.com.br/start/eyJ...",
"websdk_url_expires_at": "2026-04-29T13:30:00Z"
}kyc_status flips to pending immediately, before any documents are submitted. That is the "submission in progress" state.
Link lifetime
websdk_url is valid until websdk_url_expires_at. If the end customer doesn't open it in time, call POST /v1/users/{id}/kyc again to generate a new link. Submission state stays pending, no progress is lost.
3. Redirect the end customer
Open websdk_url in a browser tab, in an iframe, or in a mobile webview. The flow guides the customer through document capture, selfie, and liveness check, then closes itself.
The end customer doesn't return to your app with a status; the verification doesn't know your URLs. The result arrives via webhook (next step) or by polling GET /v1/users/{id}/kyc.
4. Receive the result via webhook
When verification completes the review, BlendFi updates kyc_status and dispatches a webhook to your registered URL.
{
"type": "user.kyc_approved",
"user_id": "01J...",
"external_id": "your-user-001",
"kyc_status": "approved",
"approved_at": "2026-04-29T13:08:00Z",
"expires_at": "2027-04-29T13:08:00Z"
}Possible event types:
user.kyc_pending: submission registereduser.kyc_approved: approveduser.kyc_rejected: rejected, withrejection_reasonin payloaduser.kyc_expired: approval validity has lapsed
If you don't have webhooks configured yet, poll as a fallback:
curl $BLENDFI_BASE/v1/users/01J.../kyc \
-H "Authorization: Bearer $BLENDFI_KEY"{
"user_id": "01J...",
"status": "approved",
"approved_at": "2026-04-29T13:08:00Z",
"expires_at": "2027-04-29T13:08:00Z"
}5. (For offramp) Wait for crypto KYC to run
BlendFi initiates the crypto KYC automatically as soon as the platform KYC is approved. You don't need any additional call. Under normal conditions, by the time the end customer attempts the first offramp operation, the crypto KYC is already approved and the operation proceeds.
If the offramp attempt happens during the window when the crypto KYC is still running, you receive crypto_kyc_pending (422). Wait a few seconds and retry. In rare cases, the crypto KYC may be declined and you receive crypto_kyc_declined (422); contact support with the user_id.
For onramp-only end customers, the crypto KYC is not required and does not block operations.
6. Transact
With kyc_status: approved (and crypto KYC ready, for offramp), the end customer can be the subject of a quote, transaction, and any endpoint that moves money. Before that, those endpoints respond with a clear error pointing back to the KYC step.
Forcing KYC outcomes in sandbox
The sandbox uses a mirror of the verification. To avoid waiting for a real document review, use the test helper to force any outcome:
curl -X POST $BLENDFI_BASE/v1/test_helpers/users/01J.../kyc \
-H "Authorization: Bearer $BLENDFI_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "content-type: application/json" \
-d '{"status": "approved"}'Accepted values: "approved", "rejected", "expired". The webhook fires exactly as in production, so your handler logic is exercised end-to-end.
Sandbox only
/v1/test_helpers/* does not exist in production. Make sure code paths that force KYC outcomes are marked test-only and never run against api.blendfi.com.br.
Renewal and expiration
Approved KYCs are valid for 12 months by default. BlendFi monitors expiration and moves the user to kyc_status: expired on the cutoff day. You receive a user.kyc_expired webhook 7 days before expiration so you can prompt the customer to reverify in advance.
Reverification is a fresh POST /v1/users/{id}/kyc call. The end customer goes through the same flow. There is no separate "renewal" endpoint.
Rejections
A rejected result includes a rejection_reason:
{
"user_id": "01J...",
"status": "rejected",
"rejected_at": "2026-04-29T13:08:00Z",
"rejection_reason": "document_unreadable"
}Common reasons:
rejection_reason | What happened | What to tell the end customer |
|---|---|---|
document_unreadable | Photos blurry, glared, or cropped | Retake with better light and the full document in frame |
document_expired | Submitted document is past validity | Submit a current document |
liveness_failed | Selfie didn't match the document, or was a photo-of-a-photo | Try again with the live face on camera |
pep_match | End customer matched a politically exposed persons list | Escalated by BlendFi; you'll hear from support |
sanctions_match | End customer matched a sanctions list | End customer cannot be onboarded |
data_mismatch | Document data doesn't match the name or CPF provided | Verify your external_id, name, cpf inputs |
A rejected end customer can resubmit (POST /v1/users/{id}/kyc again). The new submission supersedes the rejection; status returns to pending.
pep_match and sanctions_match are terminal; resubmission does not resolve them. Handled case by case.
Next steps
KYC overview
The two tiers (platform and crypto), states, webhooks, and errors on one page.
Quickstart
Create your first user and trigger the first KYC.
Webhooks
Signature verification with X-Blendfi-Signature, retries, replay window.
Errors and retries
How to handle kyc_required, crypto_kyc_pending, and the rest of the codes.
FAQ
Can the end customer resubmit if rejected?
Yes, for most reasons. Call POST /v1/users/{id}/kyc again to generate a new link; the customer goes through the flow again. Terminal cases: pep_match and sanctions_match, which require human review.
What happens if the end customer closes the verification mid-flow?
kyc_status stays pending. The flow holds partial state for about 24 hours; reopening the same link within that window resumes where it left off. After expiration, generate a new link.
Do I need to verify the webhook signature?
Yes. BlendFi webhooks carry the X-Blendfi-Signature header (HMAC-SHA-256 over {t}.{raw_body}, with a per-endpoint secret). Reject any delivery whose signature doesn't match. Details in Webhooks.
Why does not_started exist if I always start KYC right after creating the user?
Some integrations create users early (e.g. waitlist signup) and only trigger KYC when the end customer takes a money-relevant action. The two-step shape supports that pattern. If you always start it immediately, treat not_started as a transient state your UI never displays.
Can I see the rejected document images? No. The images stay with the verification provider. BlendFi receives only the verdict and the rejection reason. It's an intentional privacy boundary.
The end customer passed KYC in another product I operate, can I skip BlendFi's KYC? No. We reverify per BlendFi organization, regardless of whether the same end customer was verified elsewhere. Regulatory requirement.
Do I need any call for the crypto KYC?
No. Crypto KYC is initiated automatically after platform KYC is approved. You only observe the result when an offramp operation fails with crypto_kyc_required, crypto_kyc_pending, or crypto_kyc_declined, which are edge situations.
