Chapter 10: Identity Verification
Strategic Decision: Capxul Owns Identity Verification
Section titled “Strategic Decision: Capxul Owns Identity Verification”This is the most important decision in this chapter and it is already made.
The Compliance Argument
Section titled “The Compliance Argument”HoneyCoin operates as a B2B payment infrastructure provider. Their model works like Paystack or Flutterwave: HoneyCoin verifies Capxul as a business customer, and Capxul is contractually responsible for knowing its end users. If a payout is later flagged as suspicious, HoneyCoin points to their agreement with Capxul. Without its own KYC records, Capxul has no defense.
The Facade Argument
Section titled “The Facade Argument”If identity verification is delegated to each ramp provider, the facade pattern breaks at the most user-visible layer. Adding a new provider would require every user to re-verify. Swapping a provider mid-transaction is impossible. When Capxul owns verification, users verify once. The provider never needs to know about user identity.
The Commercial Argument
Section titled “The Commercial Argument”Owning verification data gives Capxul leverage with providers. Pre-verified users may unlock higher limits, lower fees, or faster settlement.
Open Question: HoneyCoin’s KYC Requirements
Section titled “Open Question: HoneyCoin’s KYC Requirements”If HoneyCoin independently requires individual-level verification for certain operations (e.g., virtual account creation), the architecture supports dual verification: Capxul’s KYC via Shufti Pro for platform-level access, plus a HoneyCoin-specific step if needed. This must be resolved on the HoneyCoin call. Most likely, HoneyCoin’s B2B API requires no individual KYC — Capxul’s own verification is sufficient.
Verification Tiers and Progressive Disclosure
Section titled “Verification Tiers and Progressive Disclosure”Capxul’s verification framework is jurisdiction-agnostic and tiered. Verification is triggered by fiat involvement and scales with transaction volume, not by platform participation. Each jurisdiction’s thresholds are configured in the jurisdictionThresholds table in Convex.
Individual Verification Tiers
Section titled “Individual Verification Tiers”Tier 0: No Verification. Required for progressive disclosure Levels 0-2 when the user does not touch fiat. Employees, vendors, and contractors who only interact with crypto need zero verification. Data collected: email and org membership.
Tier 1: Basic Verification. Required for first fiat off-ramp at low volumes. Data collected: full legal name, date of birth, phone number, country. Verification method: database lookup (seconds, no document upload). Jurisdictional thresholds define per-transaction and cumulative limits per country — see Appendix C for launch market values.
Tier 2: Standard Verification. Required when off-ramp volumes exceed Tier 1 thresholds. This is where most regular employees land. Data collected: everything from Tier 1 plus government-issued photo ID and selfie with liveness check. Verification: document OCR + liveness detection via Shufti Pro, cross-referenced against government databases (Nigeria BVN/NIN, Kenya IPRS, Ghana GhanaCard system).
Tier 3: Enhanced Verification. Required for high-volume transactions, specific jurisdictions, or PEP/sanctions flags. Data collected: everything from Tier 2 plus proof of address, source of funds declaration, AML screening. Exists in the data model but not expected to be common at launch.
Organization Verification (KYB)
Section titled “Organization Verification (KYB)”Trigger: when the org touches fiat. An org that only operates in USDC needs no KYB.
Data collected: business registration number (varies by jurisdiction), company name and address, director/UBO information, business type, certificate of incorporation. See Appendix C for registry details per launch market.
Verification: Shufti Pro’s KYB module checks against corporate registries across 250+ jurisdictions. UBO verification triggers individual KYC on identified beneficial owners.
Mapping to Progressive Disclosure
Section titled “Mapping to Progressive Disclosure”| Level | Verification Required |
|---|---|
| 0 (No account / Added by org) | None |
| 1 (Basic account / Claim-ready) | None |
| 2 (Payment-ready / Off-ramp configured) | Tier 1 individual KYC, or KYB for orgs enabling fiat |
| 3 (Full compliance) | Tier 2/3 triggered when volume approaches Tier 1 threshold |
Key UX principle: Verification is never a wall. It is a gate that appears only when needed, proportional to the action. An employee off-ramping $20/month may never see a document upload screen.
Verification Facade Architecture
Section titled “Verification Facade Architecture”Same facade pattern as fiat ramp and bridge. Shufti Pro is the sole provider at launch.
Provider Interface
Section titled “Provider Interface”- initiateIndividualVerification(params) — starts KYC session. Returns session URL or widget config.
- initiateBusinessVerification(params) — starts KYB session.
- getVerificationStatus(sessionId) — polls status: pending, in_review, approved, rejected, expired.
- parseWebhook(payload, signature) — normalizes provider webhook.
- getVerificationDetails(sessionId) — retrieves detailed results for record-keeping.
Provider Registry
Section titled “Provider Registry”Convex table verificationProviders: provider ID, supported types (kyc_tier_1/2/3, kyb), supported countries, API config, active flag, priority. At launch: Shufti Pro, all types, covering the initial launch markets. See Appendix C for country-specific coverage.
Convex Schema: verificationRecords Table
Section titled “Convex Schema: verificationRecords Table”Source of truth for all verification state. Provider records are secondary.
Schema
Section titled “Schema”Record ID, actor type (individual/organization), actor ID, verification type (kyc_tier_1/2/3, kyb), status, provider ID, provider session ID, country code, initiated/completed/expires timestamps, rejection reason/code, verified data (structured object), attempt count, previous record ID (for re-verification chains).
Status Lifecycle
Section titled “Status Lifecycle”not_started -> pending -> in_review -> approved | | | v v v expired rejected expired | v not_started (retry)Tier 1 database lookups may skip in_review (near-instant results). Approved verifications expire after 12-24 months and trigger re-verification.
Querying Verification Status
Section titled “Querying Verification Status”Every fiat-touching operation calls getActorVerificationLevel(actorType, actorId, countryCode):
- Query
verificationRecordsfor the actor - Find highest tier where status is
approvedandexpires_atis in the future - Return effective tier (0/1/2/3)
If insufficient: reject with { code: "VERIFICATION_REQUIRED", requiredTier, currentTier, reason }. The frontend routes the user to the appropriate flow.
Integration with Other Layers
Section titled “Integration with Other Layers”Fiat Ramp Facade
Section titled “Fiat Ramp Facade”The verification check runs before any provider interaction:
- Employee off-ramp: Check individual KYC tier against jurisdictional thresholds
- Org off-ramp/on-ramp: Check org KYB status
- Auto-routing: Check on every execution (verifications expire between grant and execution)
No quote is requested, no provider deposit address is generated, until the user is verified.
Event Indexing
Section titled “Event Indexing”The indexer does not directly interact with verification. Cumulative volume per user per jurisdiction is computed from fiatTransactions records in Convex. When a new off-ramp is requested, the backend sums completed off-ramps for the current period and checks against thresholds.
Financial Document Layer
Section titled “Financial Document Layer”Receipts generated on off-ramp completion can include verification metadata for audit: “This payout was made to a user verified at Tier 2 (record ID: xyz, verified: date, provider: Shufti Pro).”
Shufti Pro Integration
Section titled “Shufti Pro Integration”Capabilities
Section titled “Capabilities”KYC: document verification across 10,000+ document types in 230+ countries, face verification with 3D liveness, address verification, AML screening.
KYB: business verification against 300+ data sources in 250+ jurisdictions, UBO identification, company officer verification.
Integration Approach
Section titled “Integration Approach”Tier 1: Shufti Pro’s eIDV endpoint (database lookup without document upload). If eIDV doesn’t cover all four countries, fall back to self-declaration with verification deferred to Tier 2.
Tier 2: Shufti Pro’s Journey Builder — hosted verification flow (iframe or redirect) with government ID + face verification + 3D liveness.
Tier 3: Same as Tier 2 plus address verification and AML screening modules.
KYB: Shufti Pro’s API. Org admin provides registration number and country. Server-side verification, no user-facing UI beyond the input form.
Jurisdictional Coverage
Section titled “Jurisdictional Coverage”Shufti Pro provides KYC coverage across 230+ countries and KYB verification against 300+ data sources in 250+ jurisdictions. For specific ID types and registry details per launch market, see Appendix C.
Webhook Processing
Section titled “Webhook Processing”Single Convex HTTP action endpoint. Validates signature, normalizes payload, looks up verification record by provider session ID, transitions status. Idempotent: if already in target state, no-op.
Polling fallback: Scheduled function every 5 minutes checks records stuck in pending or in_review longer than expected. Calls provider’s getVerificationStatus. Same pattern as fiat ramp webhook reliability.
Jurisdictional Threshold Configuration
Section titled “Jurisdictional Threshold Configuration”Convex table jurisdictionThresholds: country code, currency, Tier 1/2 per-transaction limits, cumulative limits and periods, Tier 3 required flag, KYB required flag, legal reference text.
At launch: conservative defaults (require Tier 2 for any fiat transaction). Relax to Tier 1 for small amounts once thresholds are legally confirmed. Designed to be permissive later, not tightened — better to require more verification initially and relax.
Threshold Check Logic
Section titled “Threshold Check Logic”- Look up user’s country in
jurisdictionThresholds - Sum completed off-ramp volume for current period from
fiatTransactions - Determine minimum tier required based on existing + new transaction volume
- Compare against user’s current verified tier
- Proceed if sufficient; reject with
VERIFICATION_REQUIREDif not
UX Flows
Section titled “UX Flows”Employee First Off-Ramp (Tier 1)
Section titled “Employee First Off-Ramp (Tier 1)”“To withdraw to your bank account, we need to verify your identity. This takes less than a minute.” Collects name, DOB, phone, country. No document upload. If database check passes: proceed to withdrawal. If fails: prompt for Tier 2 immediately.
Employee Tier Upgrade (Tier 1 to Tier 2)
Section titled “Employee Tier Upgrade (Tier 1 to Tier 2)”“Your withdrawal volume requires additional verification. Please verify your ID. This takes about 2 minutes.” Opens Shufti Pro hosted flow. User uploads ID and takes selfie. Typically 30-60 seconds for automated check.
Org KYB
Section titled “Org KYB”“To enable fiat features, please provide your business registration details.” Collects country, registration number, company name. Backend calls Shufti Pro. Results in seconds for companies with digital registry records.
Verification Expiry
Section titled “Verification Expiry”30-day advance prompt. On expiry, next fiat operation triggers re-verification with pre-filled data from previous verification.