QvaPay API
Withdraw

Listar y Crear Retiros

Listar los retiros del usuario o crear un nuevo retiro.

GET /withdraw

Retorna la lista paginada de retiros del usuario autenticado, ordenados del más reciente al más antiguo.

Autenticación

Bearer Token (Authorization: Bearer {token}).

Query parameters

ParámetroTipoDefaultDescripción
pagenumber1Número de página (≥ 1)
takenumber20Resultados por página (1 - 30)
statusstringFiltra por estado: pending, processing, paid, cancelled
payment_methodstringFiltra por tick de la moneda (ej: BTC, USDT)
include_totalbooleanfalseSi es true, incluye total y total_pages en meta

Request

curl -X GET "https://api.qvapay.com/withdraw?page=1&take=20&status=paid&include_total=true" \
  -H "Authorization: Bearer {tu-token}"

Response

{
  "result": "OK",
  "data": [
    {
      "id": "12345",
      "amount": 50.00,
      "receive": 48.50,
      "payment_method": "USDT",
      "status": "paid",
      "details": {
        "wallet": "TXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      },
      "tx_id": "abc123def456...",
      "evidence_url": "https://tronscan.org/#/transaction/abc123def456...",
      "transaction_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "created_at": "2026-05-20T14:30:00.000Z",
      "updated_at": "2026-05-20T15:00:00.000Z"
    }
  ],
  "meta": {
    "page": 1,
    "take": 20,
    "count": 1,
    "total": 1,
    "total_pages": 1
  }
}

Campos de respuesta

CampoTipoDescripción
resultstringEstado de la operación
data[].idstringID del retiro (BigInt serializado como string)
data[].amountnumberMonto solicitado en USD
data[].receivenumberMonto a recibir después de fees
data[].payment_methodstringTick de la moneda del retiro
data[].statusstringpending, processing, paid o cancelled
data[].detailsobjectDetalles del retiro ya parseados (wallet, etc.)
data[].tx_idstring | nullID de transacción on-chain (si aplica)
data[].evidence_urlstring | nullURL del explorador o evidencia (si aplica)
data[].transaction_uuidstring | nullUUID de la transacción QvaPay asociada
data[].created_atstringFecha de creación (ISO 8601)
data[].updated_atstringFecha de última actualización (ISO 8601)
meta.pagenumberPágina actual
meta.takenumberTamaño de página solicitado
meta.countnumberResultados devueltos en esta página
meta.totalnumberTotal de retiros que cumplen el filtro (solo con include_total=true)
meta.total_pagesnumberTotal de páginas (solo con include_total=true)

Errores

CódigoDescripción
400Parámetros inválidos (take fuera de rango o status no permitido)
401Token ausente o inválido

POST /withdraw

Crea un nuevo retiro de fondos. El retiro se procesa según el método de pago seleccionado.

Autenticación

Bearer Token (Authorization: Bearer {token}) o Credenciales de App (app-id + app-secret en headers).

Cuando se usa Bearer Token, el parámetro pin es obligatorio. Con credenciales de App, el PIN no es requerido.

Request

{
  "amount": 50.00,
  "pay_method": "tron_usdt",
  "details": {
    "wallet": "TXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  },
  "pin": "1234",
  "note": "Retiro semanal",
  "webhook": "https://mitienda.com/webhook/withdraw"
}

Parámetros

ParámetroTipoRequeridoDescripción
amountnumberSiMonto a retirar (1 - 100,000 USD)
pay_methodstringSiTick de la moneda (ej: tron_usdt, btc, sol)
detailsobjectSiDetalles del retiro (dirección de wallet, etc.)
pinstringSolo BearerPIN de 4 dígitos o código OTP de 6 dígitos
notestringNoNota descriptiva del retiro
webhookstringNoURL válida para recibir notificaciones del estado del retiro
idempotency_keystringNoClave de idempotencia (8-64 caracteres, [A-Za-z0-9._-]) para reintentos seguros en conexiones inestables. Repetir la misma clave en 24h devuelve el retiro original con duplicate: true en vez de debitar de nuevo
complianceobjectNo*Atestación de cumplimiento (OFAC / CACR). Ver Cumplimiento. *Obligatoria para el navegador web en retiros cripto y a Cuba; opcional (recomendada) para apps móviles, apps de comercio y clientes API
dest_labelstringNoDeclaración del titular de la cuenta destino: OWN_ACCOUNT (cuenta propia) o THIRD_PARTY (un tercero). Si no se envía y compliance trae W-1, se registra OWN_ACCOUNT

Response

{
  "result": "OK",
  "data": {
    "withdraw_id": 12345,
    "transaction_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "receive_amount": 48.50,
    "receive_amount_coin": 48.50,
    "fee_to_apply": 1.50,
    "amount": 50.00,
    "coin": "tron_usdt"
  }
}

Campos de respuesta

CampoTipoDescripción
withdraw_idnumberID del retiro creado
transaction_idstringUUID de la transacción asociada
receive_amountnumberMonto a recibir en USD después de fees
receive_amount_coinnumberMonto a recibir en la moneda seleccionada
fee_to_applynumberComisión aplicada al retiro
amountnumberMonto original solicitado
coinstringTick de la moneda del retiro

Idempotencia

Si se envía idempotency_key y un retiro ya fue creado con esa misma clave, la respuesta es 200 con los datos del retiro original y el campo adicional "duplicate": true (no se debita saldo de nuevo). Si el retiro original aún está en proceso, se responde 409 con "code": "DUPLICATE_REQUEST" — reintenta unos segundos después.

Cumplimiento (compliance)

QvaPay registra, junto a cada retiro, una autocertificación del cliente exigida por la normativa OFAC (31 CFR Part 515) y por el programa BSA/AML de la plataforma. El objeto compliance viaja en el mismo body del retiro (en envíos multipart, dentro del JSON de payload).

{
  "amount": 58.41,
  "pay_method": "USDTBSC",
  "details": { "Wallet": "0x7dba714e606Ac161f0FDe1A056F3Fd12Db3848D3" },
  "pin": "1234",
  "compliance": {
    "purposeCode": "W-1",
    "attestations": {
      "notProhibitedOfficial": true,
      "notProhibitedPartyMember": true,
      "notRestrictedList": true,
      "lawfulPurpose": true
    },
    "purposeNote": "Billetera personal",
    "language": "es"
  }
}

Campos de compliance

CampoTipoRequeridoDescripción
purposeCodestringCódigo de propósito (ver tabla). Debe ser válido para el tipo de retiro: W-1 en salidas cripto, P-1P-4 en destinos con valor a Cuba
attestationsobjectLas cuatro casillas deben ser true: notProhibitedOfficial, notProhibitedPartyMember, notRestrictedList, lawfulPurpose. Si falta alguna, el payload se ignora íntegro (no se registra a medias)
purposeNotestringNoNota libre del cliente sobre el propósito (máx. 200 caracteres)
languagestringNoIdioma en que el cliente leyó el texto de la atestación (es por defecto)
specificLicensestringNoNúmero de licencia específica de OFAC, si la operación se ampara en una
generalLicensestringNoLegado. Cita 31 CFR (515.570, 515.542) que enviaban las apps antiguas en lugar de purposeCode. Se sigue aceptando en destinos Cuba; no aplica a salidas cripto

Códigos de propósito

CódigoAplica aSignificado
W-1Retiros cripto (on-chain)La dirección de destino es una billetera de autocustodia bajo control del titular. No es un tercero
P-1Destinos con valor a CubaRemesa familiar
P-2Destinos con valor a CubaPago a un emprendedor o negocio del sector privado cubano
P-3Destinos con valor a CubaDonativo, sin contraprestación
P-4Destinos con valor a CubaRecarga o servicios de telecomunicaciones

Con un purposeCode que no corresponde al tipo de retiro (p. ej. P-1 en un retiro cripto) el payload se descarta y el retiro se trata como enviado sin atestación.

Cuándo es obligatoria

La exigencia se despliega por canal para no romper integraciones existentes:

CanalCómo se identificaRetiro cripto sin W-1
Navegador web (app.qvapay.com)Sesión con cookie400 W1_ATTESTATION_REQUIRED
Apps móviles oficialesHeader x-qvapay-client-platform: ios | androidAceptado (se registra la ausencia)
Apps de comercioHeaders app-id + app-secretAceptado (se registra la ausencia)
Clientes API con Bearer propioAuthorization: Bearer sin cookie de sesiónAceptado (se registra la ausencia)

Para apps y clientes API la atestación es opcional hoy y recomendada: QvaPay anunciará con antelación cuando pase a ser obligatoria para todos los canales. Envíala desde ya si tu integración muestra al usuario el texto de la atestación. La atestación nunca se asume por defecto: si no llega, no se registra certificado alguno.

Otros controles regulatorios en el retiro

ControlUmbralEfecto
Verificación de identidad (KYC)Cualquier montoUna cuenta sin KYC aprobado recibe 403 con code: "KYC_REQUIRED" y kyc_url
Travel Rule (31 CFR § 1010.410)Retiros de $3,000 o más, en cualquier railEl titular debe tener nombre y dirección de calle en su ficha (Configuración → Datos de tu cuenta); si no, 400 TRAVEL_RULE_SENDER_ADDRESS. En rails fiat, details debe incluir además nombre y dirección de calle del beneficiario; si no, 400 TRAVEL_RULE_RECIPIENT_ADDRESS
Factura en retiros bancariosBANK / BANK_EUR de $3,000 o másEnvío multipart/form-data con el JSON del retiro en el campo payload y un PDF (máx. 10 MB) en invoice

Notas

  • Validación de wallet: Las direcciones de wallet para criptomonedas son validadas antes de crear el retiro.
  • Rate limit: 1 solicitud cada 5 segundos por usuario.

Errores

CódigoDescripción
400Datos inválidos (monto fuera de rango, wallet incorrecta, balance insuficiente, PIN incorrecto)
400W1_ATTESTATION_REQUIRED: retiro cripto desde el navegador sin atestación W-1 (ver Cumplimiento)
400TRAVEL_RULE_SENDER_ADDRESS / TRAVEL_RULE_RECIPIENT_ADDRESS / TRAVEL_RULE_UNAVAILABLE: retiro de $3,000 o más sin los datos que exige la Travel Rule
403KYC_REQUIRED: la cuenta no tiene verificación de identidad aprobada (kyc_url indica dónde completarla)
403La app no tiene habilitado allowed_withdraw, o la sesión es demasiado reciente (espera 5 minutos tras iniciar sesión)
409DUPLICATE_REQUEST: ya hay un retiro en proceso con la misma idempotency_key
429Límite de solicitudes excedido (máximo 1 cada 5 segundos)
500Error interno del servidor