The ANDX widget uses SumSub as the user-facing KYC vendor — same vendor as every other vakotrade KYC surface — while Alfred PSP runs as the fiat-on/off-ramp behind it. Alfred has its own KYC pipeline that we drive in reliance mode: after SumSub completes user verification, vakotrade BE forwards the identity data to Alfred via their reliance KYC API, Alfred runs AiPrise sanctions/PEP screening in the background, and the result is mirrored into our DB as a kyc-property. The user never sees Alfred's KYC UI. This document covers the decision, the bridge architecture, the open Alfred-side blockers, and the operational concerns the whole team should be aware of.
SumSub feeds KYC into vakotrade; vakotrade forwards a reliance handoff to Alfred. Payments then flow through Alfred and back to us via signed webhooks.
kyc module.kyc_status: approvedUpdateUserKycOperation writes the kyc-property and emits NotificationTrigger.kyc_approved on the event bus.PspAlfredEventsProcessor listens to kyc_approved and runs the 5-stage Alfred provisioning: country gate → create customer → submit KYC text → pull photos from S3 + POST /kyc/file → finalize via /kyc/status/circle./psp/alfred/webhook with eventType: KYC, status: APPROVED|REJECTED. Handler writes alfred_kyc_status into user_kyc_data. Widget unblocks/blocks based on this.GET /psp/alfred/eligibility — re-checks platform KYC + Alfred KYC + country + customer provisioned. Renders form or blocker.POST /psp/alfred/session binds the cached session to the trader JWT. All subsequent calls re-bind through requireSession(session_id, traderId).POST /onramp returns virtual deposit address + bank instructions. Withdrawal: enqueues canonical WithdrawalFiatCreateJob chain; /offramp only runs after auto-approve clears.ON_CHAIN_COMPLETED (onramp) → DepositManualCreateJob with DB uniqueness on remote_txid. FIAT_TRANSFER_COMPLETED (offramp) → WithdrawalCompleteJob. FAILED → WithdrawalRejectJob + balance restored.Five actors, seventeen messages. SumSub captures and stores; vakotrade BE forwards text and pulls photos from S3 to relay to Alfred.
submissionId for the customer — that's when POST /kyc/file becomes valid against Alfred's reliance API.Click each card to expand. Each one names the decision, the rationale, and the trade-off it carries.
This isn't a free architectural choice — we are obligated to take this approach. The client (ANDX) refuses to switch from SumSub to Alfred's KYC provider and refuses to embed Alfred's widget. SumSub must remain the single user-facing KYC vendor across all of vakotrade.
Alfred operates as the fiat PSP — it has its own KYC pipeline by default, but we drive it in reliance mode: SumSub completes verification, we forward the captured identity data to Alfred, and Alfred runs their internal sanctions/PEP screen (AiPrise) on top. The user never lands on Alfred's UI.
kycSubmission shape).APPROVED / REJECTED status comes back via webhook.address_country is missing or not in ALFRED_SUPPORTED_COUNTRIES. Logs ALFRED_KYC_SKIPPED_UNSUPPORTED_COUNTRY.POST /customers/create { email, type:"INDIVIDUAL", country }; alfred_customer_id persisted to users_properties. Recovery path: 409 "Customer already registered" → look up by email+country.POST /customers/{id}/kyc with the SumSub-derived field set per country (firstName, lastName, dateOfBirth, address, city, state, zipCode, phoneNumber, pep, dni, plus country-specific: cuit for AR, cpf for BR, typeDocumentCol for CO, email for CN/HK/US). Returns submissionId; cached in Redis.POST /customers/{id}/kyc/file. Shipped 2026-05-29 for individuals after Alfred granted the api-key scope (was Bug #3, now resolved). KYB (business customers) file pipe is tracked separately and remains TODO./kyc/status/circle — flips Alfred-side review to under-review. In sandbox we additionally POST /webhooks {KYC, COMPLETED} (via the dev approve-kyc endpoint) to fast-forward AiPrise.The handler can fire multiple times for the same user (event replay, retry after partial failure). Each stage checks whether its outcome is already persisted before calling Alfred — re-running on a fully provisioned user is a no-op.
kyc_status — platform-level, written by SumSub flow. Values: approved / pending / rejected / under_review.alfred_kyc_status — Alfred-side, written by inbound webhook. Values: APPROVED / PENDING / REJECTED.SumSub approval doesn't automatically mean Alfred approves — Alfred's AiPrise screen runs on top and can reject a user SumSub passed. The widget gates on both: only users with platform approved AND Alfred APPROVED see the deposit/withdrawal form. Distinct error codes (USER_NOT_VERIFIED vs ALFRED_KYC_PENDING vs ALFRED_KYC_REJECTED) let the FE render the right blocker.
ALFRED_SUPPORTED_COUNTRIES set in alfred.constants.ts: MX · AR · BR · CO · DO · US · CN · HK · CL · PE · BO · PY. Anyone else is dropped before any Alfred call. Skip is logged, not thrown.GET /allConfigs drives our rail picker — what's listed as onRampSupported / offRampSupported determines whether the deposit / withdrawal option even renders.ALFRED_CROSS_BORDER_NOT_SUPPORTED.Signature: t=<unix>,s=<hex>; we HMAC-SHA256 the body with alfred_webhook_secret. Replay protection via 5-minute timestamp window.Lock("ALFRED_WEBHOOK:{referenceId}:{status}") deduplicates concurrent deliveries.DepositManualCreateJob, paymentsService.findOneMaster({ psp_service_id: 'ALFRED', type: 'deposit', remote_txid }). Re-delivery returns the existing Payment row — no double-credit.to_amount goes through Number.isFinite() && > 0 before crediting; failure logs ALFRED_ONRAMP_INVALID_AMOUNT and throws.WithdrawalFiatCreateJob — debits the user, freezes balance, persists Alfred session context.WithdrawalAutoApproveJob — runs platform-wide limits, KYT/AML, Travel Rule.WithdrawalFiatPspRequestJob — only at this point does Alfred's /offramp get called. The widget polls in the meantime.WithdrawalCompleteJob (completed) or WithdrawalRejectJob (reversed + balance restored).Same KYT/limits/Travel-Rule pipeline that protects crypto withdrawals — no fork in compliance logic, no duplicated controls. Alfred is just one more PSP-routed branch off the existing trunk.
The session has a 24h TTL — state can change in that window (KYC revoked, maintenance turned on, withdrawals frozen). Every initiate-deposit / initiate-withdrawal call re-runs the same gate stack the GraphQL withdrawal endpoint runs, inline in the service since most NestJS guards are GraphQL-only.
settingsService.maintenance_mode)alfred_customer_id exists)depositAddress (verified 2026-05-25 — sample 0x0C065CB462Ed093b2F60aa9Ad3F20A5e10627F5F on Polygon resolves to a funded EOA with 14 mainnet tx, nonce 0xe). There is no testnet sandbox to send synthetic crypto to./onramp with 111406.Five dev-only routes under /psp/alfred/__dev/*, guarded by an exchange check (VAKOTEST / *UAT only) and a shared X-Dev-Secret header. They drive the lifecycle: provision-customer, seed-kyc-and-provision, approve-kyc (push KYC COMPLETED webhook), emit-onramp (full webhook chain), emit-offramp. Full details and live "Fire" buttons in the QA Test Guide.
Identical end-to-end flow (create → KYC submit → push KYC COMPLETED webhook) on five fresh customers: MX and BR returned POST /onramp → 201; AR, CO, and DO returned 400 / 111301 UNKNOWN_ERROR (404 in AR's case). Reported to Alfred with full curl reproduction.
Alfred shipped the fix. The 2026-05-29 retest reran the same 4-step flow on fresh AR / CO / DO customers — all three now return 201 with a real transaction and bank-payment instructions, same shape as MX / BR. The 5-country deposit matrix is fully green.
409 / 111485 "Error creating external account" with valid US routing.422 / 110003 "Customer not enabled for USA accounts".US withdrawal is not testable today. Cascade: without a fiat account no offramp can be initiated. Possibly business-level enablement gating on Alfred's side that needs to be flipped per our businessId.
POST /customers/{id}/kyc/file returned 400 / 111301 for every payload variant (4 JSON shapes, 4 multipart shapes, 2 alt endpoints). Root cause: /customers/*/kyc/file scope was not enabled on our restricted-dev api-key.
Alfred granted the scope on our api-key. We shipped the SumSub → multipart pipe for individuals: POST /customers/{id}/kyc/file now returns 201 for SELFIE, ID_FRONT, ID_BACK. AiPrise pipeline flips customers to COMPLETED after the canonical 3 files land. KYB (business customers) file upload remains a TODO and is tracked separately — not a regression on the individual reliance flow.
Alfred PSP integration is SumSub-led, reliance-model: SumSub completes the user-facing KYC, vakotrade BE forwards the identity data + photos to Alfred via their reliance API, and Alfred's AiPrise screen runs in the background. The user never sees Alfred's KYC UI. Five fiat rails are wired (SPEI · PIX · COELSA · ACH-PSE · ACH_DOM) and all five are green for both deposit and withdrawal after the 2026-05-29 retest.
One Alfred-side blocker remains: US BANK_USA registration (Bug #2) — cascades into no US offramp. Bug #1 (AR / CO / DO onramp) and Bug #3 (KYC photo upload) are resolved.
Two KYC pipelines were available: (a) Alfred's own KYC — user lands on Alfred's flow, Alfred owns the identity record; or (b) reliance mode — SumSub captures everything, vakotrade BE forwards a structured identity record to Alfred, Alfred trusts our verdict and runs AiPrise on top.
alfred_kyc_status.externalId).POST /liquidation-addresses-business. Once set, every subsequent deposit routes there automatically.users_properties — alfred_customer_id (the per-user Alfred customer UUID).user_kyc_data — platform kyc_status AND alfred_kyc_status (dual). Plus SumSub-captured fields used to construct the reliance handoff.What's available today on Alfred's dev. Production status pending STG/PROD credentials (see §5).
| Country | Rail | Deposit | Withdrawal | Note |
|---|---|---|---|---|
| MX | SPEI | OK | OK | Full end-to-end working. |
| BR | PIX | OK | OK | Full end-to-end working. |
| AR | COELSA (ARS) | OK | OK | Full end-to-end working after Alfred shipped the Bug #1 fix. |
| CO | ACH (PSE — needs bank_id) | OK | OK | Full end-to-end with valid PSE code (e.g. 1007). Inline iframe via redirectUrl. |
| DO | ACH_DOM | OK | OK | Full end-to-end. BANCO POPULAR rail confirmed. |
| US | BANK_USA | cascades | Bug #2 | Can't register US bank → no offramp. |
| CN | BANK_CN | config off | OK * | Alfred config onRampSupported: false. * Untested — no bridged customer. |
| CL / BO / PE | ACH_CHL / ACH_BOL / B89 | config off | untested | Offramp-only by Alfred config. No bridged customer to test. |