QA
QA Helper Console
click to configure · values persist in browser
Values are stored in localStorage on this domain only — they do not leave your browser until you click a Fire button. If your browser blocks live calls (CORS), each runner falls back to a pre-filled curl you can paste into a terminal. Need a fresh JWT? Sign in to the widget as your test trader and copy the Authorization header from any GraphQL request in DevTools → Network.

Dev playground

Routes
All under /psp/alfred/__dev/*
Guard
AlfredDevGuard / AlfredDevExchangeGuard — JWT + X-Dev-Secret; VAKOTEST / *UAT only
Status
Live · interactive
Security: the dev secret rotates per environment. Never paste it into shared logs or screenshots. If QA can hit these endpoints from anywhere on the internet — investigate before continuing. Every route requires both a valid trader JWT AND a matching X-Dev-Secret header; the service-side check rejects when config.exchange is not VAKOTEST or doesn't contain UAT. Production never executes these.

Quick start — 4 steps to a working test

Open the QA Helper Console above, fill in 3 fields, then drive the lifecycle.

1
Configure once
Open the QA Helper Console at the top → paste API base URL, your Trader JWT, and the dev secret. Click Save. Values persist in your browser.
2
Provision a customer
In the seed-kyc-and-provision runner pick a country (e.g. MX), click Fire. Alfred customer created + KYC text submitted in one go.
3
Approve KYC
In the KYC status runner pick COMPLETED and Fire. Pushes the webhook to Alfred — customer transitions to COMPLETED + your widget unblocks.
4
Drive the flow
Open the widget → start deposit / withdrawal → grab the session_id. Fire emit-onramp or emit-offramp with that ID. Watch the balance change.
JWT shortcut: Sign in to the widget as your test trader → DevTools → Network → copy the Authorization header from any GraphQL request. It's a Bearer token; paste it (without the "Bearer " prefix) into the QA console.

Dev endpoints (QA only)

The /psp/alfred/__dev/* route paths. Same paths as before — a backend refactor moved them to a new controller but kept the URLs.

EndpointPurposeOutput
POST /__dev/provision-customerRun EnsureAlfredCustomerOperation for callerCustomer object incl. customerId.
POST /__dev/seed-kyc-and-provision?country=XXPre-fill KYC fields per country preset, then provisionFresh customer + seeded fields.
POST /__dev/approve-kycPush KYC COMPLETED webhook to Alfred (convenience alias)Customer transitions to COMPLETED.
POST /__dev/emit-onrampSimulate full ONRAMP webhook chain4 events fired → DepositManualCreateJob.
POST /__dev/emit-offrampSimulate OFFRAMP success/failure chainWithdrawalComplete/Reject job dispatched.
GET /__dev/statusesList allowed status valuesFor dropdown population in dev tooling.
POST /__dev/emit-kyc-statusPush an arbitrary KYC status (incl. UPDATE_REQUIRED) to Alfred using the live submissionIdWraps the raw POST /webhooks helper. Resolves customerId + submissionId server-side.

Interactive runners

Click a card to expand its Feature Breakdown. Fire live (or copy the curl). Each runner uses the config from the console above.

1
KYC status — emit-kyc-status (drive any KYC state)
DEV-TOOL 5 statuses supersedes approve-kyc
Path
POST
Resolves
customerId + submissionId
Persists
Yes
How it works
  • POST /psp/alfred/__dev/emit-kyc-status resolves your customerId + live submissionId server-side and pushes the synthetic KYC webhook to Alfred, which fans it back and updates alfred_kyc_status through the production code path.
  • Pick a status to drive the success / pending / reject / update-required flows: CREATED, IN_REVIEW, COMPLETED, FAILED, UPDATE_REQUIRED.
  • This unified runner supersedes approve-kyc. The old approve-kyc card had a Fire button but no status picker — it is just a convenience alias for "push COMPLETED". Use this runner with the dropdown for everything.
curl
POST /psp/alfred/__dev/emit-kyc-status
Authorization: Bearer <trader JWT>
X-Dev-Secret: <dev secret>
{ "status": "UPDATE_REQUIRED" }
→ 200 { customerId, submissionId, status, alfred: { message: "success" } }
🔥 Run live
2
Seed KYC + provision — seed-kyc-and-provision
DEV-TOOL 12 country presets overrides
Presets
12
Default
AR
Overrides
Body
What it does
  • Loads a country preset (firstName, lastName, address fields, country-correct ID number, phone). Available countries: AR · MX · BR · CO · CL · PE · DO · BO · PY · US · CN · HK.
  • Body fields override preset values (e.g. {"first_name":"Custom"}).
  • Clears stale country-specific properties (tax_id, type_document_col) left from previous seeds.
  • Removes any prior alfred_customer_id + cached submission so each call starts clean.
  • Creates fresh Alfred customer with a synthetic per-seed email (avoids 409 on re-runs).
  • Runs the same EnsureAlfredCustomerOperation as production.
curl
POST /psp/alfred/__dev/seed-kyc-and-provision?country=BR
Authorization: Bearer <trader JWT>
X-Dev-Secret: <dev secret>
{ "document_number": "12345678909" }   # optional overrides
→ 200 { ok: true, country: "BR", seeded: {…}, customer: {…} }
🔥 Run live
3
Provision customer — provision-customer
DEV-TOOL Exchange-gated Fire only
Path
POST
Guard
AlfredDevGuard
Exchanges
VAKOTEST · *UAT
When to use
  • Your user already has kyc_status=approved + the required country/address fields, but alfred_customer_id hasn't been created yet — this endpoint runs EnsureAlfredCustomerOperation identically to the real kyc_approved event listener.
  • Returns the Alfred customer object (with the new customerId persisted to users_properties).
curl
POST /psp/alfred/__dev/provision-customer
Authorization: Bearer <trader JWT>
X-Dev-Secret: <dev secret>
→ 200 { ok: true, kyc_type: "individual",
       customer: { customerId, statusKyc, country } }
🔥 Run live
4
Approve KYC — approve-kyc (alias for "push COMPLETED")
DEV-TOOL superseded by KYC status
Latency
~500ms
Persists
Yes
Reversible
No
Convenience alias. approve-kyc is just a one-shot "push KYC COMPLETED". The KYC status runner above supersedes it (pick COMPLETED there). Listed here for completeness.
How it works
  • Resolves the caller's last submitted submissionId from Redis (cached at submit-text time).
  • Calls Alfred's POST /webhooks { referenceId: submissionId, eventType: "KYC", status: "COMPLETED" }.
  • Alfred fans this back to our own POST /psp/alfred/webhook handler with a real HMAC signature — so the kyc-property alfred_kyc_status is updated through the production code path.
  • Result: the customer transitions to statusKyc: "COMPLETED" on Alfred's side AND alfred_kyc_status: "APPROVED" on ours.
curl
POST /psp/alfred/__dev/approve-kyc
Authorization: Bearer <trader JWT>
X-Dev-Secret: <dev secret>
→ 200 { user_id, customerId, submissionId,
       alfred: { message: "success" } }
🔥 Run live
5
Emit onramp — emit-onramp (simulate full ONRAMP chain)
DEV-TOOL success · failure Deposit
Scenarios
2
Events fired
4
Inter-step delay
1.2s
Scenarios
  • scenario: "success" walks: FIAT_DEPOSIT_RECEIVED → TRADE_COMPLETED → ON_CHAIN_INITIATED → ON_CHAIN_COMPLETED. Final event triggers DepositManualCreateJob and credits USDT.
  • scenario: "failure" fires a single FAILED event with failureReason: "QA_SIMULATED_FAILURE". Widget should show the failed-step marker on the last good status.
Prerequisites
  • The session must already exist (created via POST /psp/alfred/session) and must have transactionId (created via POST /psp/alfred/initiate-deposit).
  • Caller must own the session — traderId from JWT must match session.user_id or Alfred returns ALFRED_DEV_SESSION_NOT_OWNED.
curl
POST /psp/alfred/__dev/emit-onramp
Authorization: Bearer <trader JWT>
{ "session_id": "<…>", "scenario": "success" }
→ 200 { fired: [ "FIAT_DEPOSIT_RECEIVED", "TRADE_COMPLETED",
                "ON_CHAIN_INITIATED", "ON_CHAIN_COMPLETED" ] }
🔥 Run live
6
Emit offramp — emit-offramp (simulate full OFFRAMP chain)
DEV-TOOL success · failure Withdrawal
Scenarios
2
Hits
setWithdrawalStatus
Reversibility
on FAILED
Scenarios
  • scenario: "success" emits FIAT_TRANSFER_COMPLETEDsetWithdrawalStatus({ status: completed }) → enqueues WithdrawalCompleteJob. Payment moves to completed.
  • scenario: "failure" emits FAILED with reason → setWithdrawalStatus({ status: rejected }) → enqueues WithdrawalRejectJob. Payment moves to rejected and the user's balance is restored.
curl
POST /psp/alfred/__dev/emit-offramp
Authorization: Bearer <trader JWT>
{ "session_id": "<…>", "scenario": "failure" }
→ 200 { fired: ["FAILED"], setWithdrawalStatus: "rejected" }
🔥 Run live