BlendFi
KYC

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 registered
  • user.kyc_approved: approved
  • user.kyc_rejected: rejected, with rejection_reason in payload
  • user.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_reasonWhat happenedWhat to tell the end customer
document_unreadablePhotos blurry, glared, or croppedRetake with better light and the full document in frame
document_expiredSubmitted document is past validitySubmit a current document
liveness_failedSelfie didn't match the document, or was a photo-of-a-photoTry again with the live face on camera
pep_matchEnd customer matched a politically exposed persons listEscalated by BlendFi; you'll hear from support
sanctions_matchEnd customer matched a sanctions listEnd customer cannot be onboarded
data_mismatchDocument data doesn't match the name or CPF providedVerify 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

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.

On this page