/offramp only after auto-approve clears. QA simulates the terminal webhook via POST /psp/alfred/__dev/emit-offramp (success or failure) from the Dev playground.Step-by-step actor sequence. Color-coded lanes show who does what.
POST /fiat-accounts {type: PIX, pixKey, document, name}. Returns fiatAccountId.POST /session {type: withdrawal, …, fiat_account_id} → POST /initiate-withdrawal.WithdrawalFiatCreateJob debits user balance → WithdrawalAutoApproveJob (limits/KYT/Travel-Rule) → WithdrawalFiatPspRequestJob.WithdrawalFiatPspRequestJob calls POST /offramp on Alfred. Payment moves to processing.__dev/emit-offramp with scenario: failure.FAILED with failureReason: QA_SIMULATED_FAILURE.WithdrawalRejectJob dispatched. Balance restored. Payment rejected. Widget shows "withdrawal failed" + restored amount.POST /session. Receives session_id. Logs the ID (e.g. shares it on Slack).POST /initiate-withdrawal with User A's session_id.requireSession(session_id, traderId): data.user_id !== traderId → throws ALFRED_SESSION_NOT_OWNED.The full happy path from widget mount to terminal webhook. Steps marked QA use dev endpoints; everything else is the production widget path.
GET /psp/alfred/eligibility as deposit. Same error codes.GET /psp/alfred/fiat-accounts lists; POST /psp/alfred/fiat-accounts creates. Schema per rail comes from GET /psp/alfred/fiat-accounts/requirements. Body-shape is rail-specific (SPEI: CLABE; PIX: chave+document; ACH_DOM: cuenta+documentoTitular; BANK_USA: routing+account).POST /psp/alfred/session with type: "withdrawal". Same gate stack as deposit. Returns session_id.POST /psp/alfred/initiate-withdrawal debits the user (canonical WithdrawalFiatCreateJob) and enqueues the auto-approve pipeline. NO Alfred call yet — partner call lives in WithdrawalFiatPspRequestJob after limits / KYT / Travel-Rule clear.PspAlfredService.executeWithdrawalPspRequest creates the Alfred quote + offramp. Payment moves to processing; widget shows "sending to bank".WithdrawalCompleteJob; FAILED → WithdrawalRejectJob (re-credit + reverse).POST /psp/alfred/__dev/emit-offramp with scenario: "success" | "failure" walks the chain.Withdrawal / Offramp — MX SPEI · BR PIX · AR COELSA · CO ACH-PSE · DO ACH_DOM · US BANK_USA (blocked).
completede845ff4a-…)rejectedALFRED_WITHDRAWALS_FROZENALFRED_INCORRECT_STATUS_CHANGE — no double-debit/fiat-accounts/requirements?type=PIX returns required-field schemapixKey, documentNumber, accountName requiredfiatAccountIdc66836ae-…)ALFRED_SESSION_NOT_OWNED (traderId from JWT, not body)One open issue remains on Alfred's side after the 2026-05-29 retest. US withdrawal cannot be tested at all today.
Both bridged and non-bridged US customers fail to register a BANK_USA fiat account. Cascade: no fiat account ⇒ no offramp. Workaround: none — US withdrawal blocked entirely.
POST /psp/alfred/fiat-accounts { "type":"PIX", "session_id":"<…>", "fiatAccountFields": { "pixKey":"…", "documentNumber":"12345678909", "accountName":"…" } } POST /psp/alfred/session { "data": { "type":"withdrawal", "amount":100, "source_currency_id":"BRL", "payment_route_id":"<…>", "wallet_id":"MAIN", "fiat_account_id":"<from previous step>" } } POST /psp/alfred/initiate-withdrawal { "session_id":"<…>" } → 200 (job dispatched, payment moves through auto-approve → /offramp → webhook) POST /psp/alfred/__dev/emit-offramp { "session_id":"<…>", "scenario":"success" } → webhook chain → WithdrawalCompleteJob
scenario: success or failure to drive WithdrawalCompleteJob / WithdrawalRejectJob.