KYC/AML Verification in Crypto Mobile Application
KYC in crypto app — not just "scan passport". Combination of document verification, liveness detection, sanctions screening (OFAC, EU) and integration with licensed provider. SDK choice affects UX, conversion, regulatory compliance.
Provider Selection
Established solutions: Sumsub, Onfido, Jumio, Persona, Veriff. For crypto commonly Sumsub or Onfido — both have mobile SDK and crypto experience.
Sumsub SDK (iOS and Android) works via applicant ID created on backend:
// Android — Sumsub SDK init
val snsMobileSDK = SNSMobileSDK.Builder(this, accessToken)
.withHandlers(
onStatusChanged = { newStatus, prevStatus ->
when (newStatus) {
SNSSDKState.Ready -> Log.d("KYC", "SDK ready")
SNSSDKState.Failed.Unauthorized -> refreshToken()
SNSSDKState.FinallyRejected -> showRejectedScreen()
SNSSDKState.ApplicantSubmitted -> navigateToWaitingScreen()
else -> {}
}
},
onError = { error ->
Sentry.captureException(RuntimeException("KYC error: ${error.description}"))
}
)
.build()
snsMobileSDK.launch()
Access token lives 60 seconds — need backend endpoint to refresh (/kyc/token/refresh). If user gets stuck and token expires — SDK calls Unauthorized, need to refresh token invisibly.
Liveness Detection
Key for AML audit. Providers require confirming live human before camera, not photo or deepfake. Sumsub uses random gestures (head turn, blink). Onfido — passive analysis of skin texture and micro-movements.
In practice: users with poor lighting or old cameras often fail first attempt. Conversion drops 15–25% on budget devices. Solution — add lighting tips before liveness and give 3 attempts with failure explanation.
AML Checks
After identity verification — sanctions screening. Either built into KYC provider (Sumsub includes AML in tiers) or via Chainalysis, Elliptic, TRM Labs.
Chainalysis Reactor API checks wallet address against darknet marketplaces, mixing services, hacker addresses:
suspend fun checkWalletRisk(address: String): RiskScore {
val response = chainalysisApi.getAddressRisk(
address = address,
outputType = "SUMMARY"
)
return RiskScore(
score = response.risk,
category = response.cluster?.category,
isSanctioned = response.identifications
.any { it.category == "sanctions" }
)
}
If isSanctioned == true — block transaction and log for compliance. Not UX decision, legal requirement.
Verification Status Storage
KYC status (pending / approved / rejected / recheck_needed) on backend. Mobile caches locally, checks freshness on every start and after backgrounding (applicationWillEnterForeground / onResume).
Screen navigation depends on status. Unverified user sees limited functionality — view prices, no transactions. After submission — waiting screen with real-time status update via WebSocket or polling every 30 seconds.
Typical Errors
Most common — launch KYC SDK without checking camera availability and permissions. CAMERA permission can be revoked after first launch. Need explicit check before SDK start.
Second — not handling FinallyRejected separately from Declined. FinallyRejected means attempts exhausted, user must contact support. Declined — can try again.
KYC SDK + AML checks + UI for all statuses — 2–4 weeks. Cost estimated individually after provider choice and jurisdiction analysis.







