Guía para desarrolladores
Sitio de VEXPayReferencia de API
Explorar documentación

Convierte tu saldo en VES o COP a USDT

Convierte los bolívares o pesos colombianos que cobras con VEXPay en USDT, desde el portal o por API, cuando quieras o automáticamente con cada pago en COP. Cotiza una tasa, acéptala y recibe los USDT en tu saldo USDT de VEXPay.

Cómo funciona

Cada pago que cobras en bolívares suma a tu saldo disponible en VES (GET /v1/balance). Una conversión pasa parte de ese saldo a tu saldo USDT (GET /v1/crypto/balance) a una tasa que ves y aceptas primero. No hay transferencias bancarias y no le envías nada a VEXPay: los VES ya están en tu saldo VEXPay.

  1. Ten USDT habilitado

    Las conversiones se activan automáticamente en toda cuenta con USDT habilitado. Sin USDT, todos los endpoints de conversión responden 403 conversions_not_enabled.

  2. Cotiza

    Pide una cotización con los VES que quieres gastar o los USDT que quieres recibir. Muestra la tasa exacta y ambos montos, y vale por 60 segundos.

  3. Convierte

    Acepta la cotización. Los VES salen de tu saldo de inmediato: las conversiones pueden usar bolívares que siguen en la retención de 24 h, y los USDT comprados con ellos quedan retenidos hasta que esa retención termine. A menudo la conversión ya viene COMPLETED en la misma respuesta; si no, queda PENDING hasta que VEXPay entregue los USDT. Los montos quedan fijos: los movimientos del mercado no los cambian.

  4. Recibe los USDT

    VEXPay entrega los USDT en tu saldo USDT, la conversión pasa a COMPLETED y recibes el webhook conversion.completed.

  5. Úsalos

    Los USDT quedan en tu saldo USDT de VEXPay. Envíalos a cualquier wallet con un payout USDT (POST /v1/crypto/payouts) cuando quieras.

Estados y tus saldos

EstadoQué significaTus VESTus USDTWebhook
PENDINGAceptaste una cotización; VEXPay está entregando los USDT (no ocurre si se completa en la misma petición)Ya debitadosAún no acreditados— (la respuesta del POST)
COMPLETEDLos USDT se entregaron (final)DebitadosAcreditados con targetAmountUsdtconversion.completed
CANCELEDLa cancelaste o VEXPay no pudo completarla (final)Devueltos completosSin cambiosconversion.canceled

Ejemplo con spread de 2% y tasa de mercado 979,2450 (tasa 998,8299), convirtiendo 10.000,00 VES:

MomentoavailableVesconvertedVesUSDT disponible
Antes25.000,000,000,00
Después de POST /v1/conversions (PENDING)15.000,0010.000,000,00
Después de conversion.completed15.000,0010.000,0010,01
…o después de conversion.canceled25.000,000,000,00

convertedVes en GET /v1/balance es el neto de VES convertidos (las conversiones canceladas se restan). Las conversiones y los payouts en VES usan el mismo saldo disponible y se debitan de forma atómica, así que nunca gastan los mismos bolívares.

La tasa y lo que cuesta

  • rate son VES por 1 USDT: la tasa de mercado USDT/VES actual (marketRate) más tu spread (spreadPercent). No es la tasa oficial del BCV.
  • El spread es todo el precio de la conversión: no hay otra comisión. Lo que muestra la cotización es exactamente lo que pagas y recibes.
  • Los USDT que recibes se redondean hacia abajo al céntimo; los VES que pagas, hacia arriba.
  • Si no hay una tasa de mercado actual, la cotización responde 503 rate_unavailable: reintenta en unos minutos.
  • Tu cuenta puede tener un mínimo por conversión (amount_below_minimum) y un límite diario en VES (daily_limit_exceeded, contado desde las 00:00 hora de Caracas).

Ejemplo: mercado 979,2450 + 2% → tasa 998,8299. 10.000,00 VES → 10,01 USDT. 50,00 USDT → 49.941,50 VES.

Convertir COP a USDT

Los pesos que cobras con Bre-B, Nequi y Daviplata quedan en tu saldo COP (GET /v1/cop/balance). Conviértelos con los mismos endpoints: agrega "sourceCurrency": "COP" y envía sourceAmount (pesos enteros) o targetAmountUsdt. Tu cuenta necesita USDT y COP habilitados.

curlbash
curl -X POST "${API_URL}/v1/conversions/quotes" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --header "content-type: application/json" \
  --data '{ "sourceCurrency": "COP", "sourceAmount": "1000000" }'
Node SDKjavascript
const quote = await vexpay.conversions.quotes.create({ sourceCurrency: 'COP', sourceAmount: '1000000' });
const conversion = await vexpay.conversions.create({ quoteId: quote.id }, { idempotencyKey: 'convert-cop-001' });
Python SDKpython
quote = vexpay.conversions.quotes.create({"sourceCurrency": "COP", "sourceAmount": "1000000"})
PHP SDKphp
$quote = $vexpay->conversions->quotes->create(['sourceCurrency' => 'COP', 'sourceAmount' => '1000000']);
Respuesta (201)json
{
  "id": "0c7d3f2e-8a1b-4c5d-9e6f-1a2b3c4d5e6f",
  "object": "conversion_quote",
  "rate": "4037.6000",
  "marketRate": "3920.0000",
  "spreadPercent": "3.0000",
  "rateSource": "market",
  "sourceCurrency": "COP",
  "sourceAmount": "1000000",
  "sourceAmountVes": null,
  "targetAmountUsdt": "247.67",
  "expiresAt": "2026-10-09T15:01:00.000Z",
  "createdAt": "2026-10-09T15:00:00.000Z"
}
  • rate son COP por 1 USDT: la tasa de mercado USDT/COP más tu spread COP (puede ser distinto a tu spread VES).
  • Los COP que pagas se redondean hacia arriba al peso entero; los USDT que recibes, hacia abajo al centavo. Ejemplo: mercado 3.920 + 3% → tasa 4037.6000; 1.000.000 COP → 247.67 USDT; 50.00 USDT → 201.880 COP.
  • Al aceptar, se debitan tus COP disponibles de inmediato. Si cancelas una conversión pendiente, los COP vuelven. El límite diario en COP se cuenta desde las 00:00 hora de Bogotá.
  • sourceAmountVes es null en cotizaciones y conversiones COP; usa sourceCurrency y sourceAmount. GET /v1/conversions?sourceCurrency=COP lista solo las conversiones COP.
  • Un reembolso COP sigue necesitando saldo COP disponible. Los pesos que ya convertiste no están disponibles para reembolsos: guarda algo de COP si esperas reembolsos.

Conversión automática de pagos COP

En lugar de cotizar tú, deja que VEXPay convierta una parte de cada pago COP al completarse. Define un porcentaje entero de 0 (apagado, por defecto) a 100. Cuando un pago COP se completa, VEXPay convierte esa parte del neto (después de la comisión COP) a la tasa actual más tu spread.

curlbash
curl -X PATCH "${API_URL}/v1/conversions/settings" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --header "content-type: application/json" \
  --data '{ "autoConvert": { "COP": { "percent": 50 } } }'
Node SDKjavascript
await vexpay.conversions.settings.update({ autoConvert: { COP: { percent: 50 } } });
const settings = await vexpay.conversions.settings.retrieve();
Python SDKpython
vexpay.conversions.settings.update({"autoConvert": {"COP": {"percent": 50}}})
PHP SDKphp
$vexpay->conversions->settings->update(['autoConvert' => ['COP' => ['percent' => 50]]]);
  • Ejemplo: un pago de 100.000 COP con la comisión de 4,5% + COP 1.500 deja 94.000 COP netos. Al 50% se convierten 47.000 COP (11.64 USDT a tasa 4037.6000) y 47.000 COP quedan en tu saldo COP.
  • Recibes primero payment.completed y luego conversion.created con origin: "auto" y el paymentId (normalmente en menos de un minuto). Después llega conversion.completed, como en cualquier conversión. La conversión automática nunca retrasa ni cambia el pago.
  • Aplica solo a pagos completados después de activarla. Si la parte a convertir queda por debajo de tu mínimo en USDT, o superaría tu límite diario en COP, no se convierte y los pesos quedan en COP.
  • Si no hay tasa de mercado, VEXPay reintenta durante 24 horas; después los pesos quedan en COP.
  • Puedes cancelar una conversión automática pendiente como cualquier otra.
  • GET /v1/conversions/settings muestra también tus spreads, mínimo, límites diarios y desde qué monedas puedes convertir. Por API solo se puede cambiar autoConvert; para lo demás contacta a VEXPay.
  • En el portal, abre COP: la tarjeta "Auto-convert COP payments" define el mismo porcentaje.

Desde el portal (sin código)

  1. Abre USDT

    En el portal de VEXPay, abre USDT. Con USDT habilitado verás la tarjeta "Convert VES → USDT" con tus VES disponibles y tu mínimo.

  2. Ingresa un monto

    Elige "Spend VES" (gastar VES) o "Receive USDT" (recibir USDT), escribe el monto y pulsa Get quote.

  3. Confirma en 60 segundos

    Revisa la tasa y ambos montos y pulsa Confirm conversion. Si se acaba el tiempo, pulsa Refresh quote.

  4. Síguela

    La conversión aparece en el historial debajo de la tarjeta como pendiente, y luego completada (los USDT aparecen en tu saldo USDT) o cancelada (los VES vuelven). Puedes cancelarla desde ahí mientras esté pendiente.

Paso 01

Cotiza

Envía exactamente uno de sourceAmountVes (VES a gastar) o targetAmountUsdt (USDT a recibir), como texto con hasta 2 decimales.

curlbash
curl -X POST "${API_URL}/v1/conversions/quotes" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --header "content-type: application/json" \
  --data '{ "sourceAmountVes": "10000.00" }'
Node SDKjavascript
const quote = await vexpay.conversions.quotes.create({ sourceAmountVes: '10000.00' });
// o: { targetAmountUsdt: '50.00' }
Python SDKpython
quote = vexpay.conversions.quotes.create({"sourceAmountVes": "10000.00"})
PHP SDKphp
$quote = $vexpay->conversions->quotes->create(['sourceAmountVes' => '10000.00']);
Respuesta (201)json
{
  "id": "6f1c2b0e-4c1d-4a8e-9a51-0f4a1e9a0001",
  "object": "conversion_quote",
  "rate": "998.8299",
  "marketRate": "979.2450",
  "spreadPercent": "2.0000",
  "rateSource": "market",
  "sourceAmountVes": "10000.00",
  "targetAmountUsdt": "10.01",
  "expiresAt": "2026-10-03T15:01:00.000Z",
  "createdAt": "2026-10-03T15:00:00.000Z"
}

Una cotización es un precio, no una reserva: no aparta tus VES. Muéstrala a quien decide y acéptala solo cuando confirme.

Paso 02

Convierte

Acepta la cotización antes de expiresAt. Envía siempre un header Idempotency-Key (los SDKs lo envían por ti): si la petición expira, reintenta con la misma clave y recibes la misma conversión en lugar de convertir dos veces.

curlbash
curl -X POST "${API_URL}/v1/conversions" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --header "Idempotency-Key: convert-2026-10-03-001" \
  --header "content-type: application/json" \
  --data '{ "quoteId": "6f1c2b0e-4c1d-4a8e-9a51-0f4a1e9a0001", "reference": "tesoreria-2026-10-03" }'
Node SDKjavascript
const conversion = await vexpay.conversions.create(
  { quoteId: quote.id, reference: 'tesoreria-2026-10-03' },
  { idempotencyKey: 'convert-2026-10-03-001' },
);
Python SDKpython
conversion = vexpay.conversions.create(
    {"quoteId": quote.id, "reference": "tesoreria-2026-10-03"},
    idempotency_key="convert-2026-10-03-001",
)
PHP SDKphp
$conversion = $vexpay->conversions->create(
    ['quoteId' => $quote->id, 'reference' => 'tesoreria-2026-10-03'],
    ['idempotency_key' => 'convert-2026-10-03-001'],
);
Respuesta (201)json
{
  "id": "b7e3a9d2-5f60-4c2b-8f0e-2d1c3b4a5e6f",
  "object": "conversion",
  "status": "PENDING",
  "reference": "tesoreria-2026-10-03",
  "rate": "998.8299",
  "marketRate": "979.2450",
  "spreadPercent": "2.0000",
  "rateSource": "market",
  "sourceAmountVes": "10000.00",
  "targetAmountUsdt": "10.01",
  "createdAt": "2026-10-03T15:00:20.000Z",
  "completedAt": null,
  "canceledAt": null,
  "cancelReason": null
}

reference es opcional y tuyo (hasta 128 caracteres): úsalo para relacionar la conversión con tu propio registro. Vuelve en cada lectura y webhook.

Paso 03

Maneja el resultado

Suscribe tu endpoint de webhooks a conversion.completed y conversion.canceled (POST /v1/webhooks o el portal), y a conversion.created si usas la conversión automática. Cada uno trae la conversión en data (con sourceCurrency, sourceAmount, origin y paymentId), más livemode, y va firmado como todos los webhooks de VEXPay.

conversion.completedjson
{
  "event": "conversion.completed",
  "data": {
    "id": "b7e3a9d2-5f60-4c2b-8f0e-2d1c3b4a5e6f",
    "object": "conversion",
    "status": "COMPLETED",
    "reference": "tesoreria-2026-10-03",
    "rate": "998.8299",
    "sourceAmountVes": "10000.00",
    "targetAmountUsdt": "10.01",
    "completedAt": "2026-10-03T16:12:05.000Z",
    "livemode": true
  },
  "timestamp": "2026-10-03T16:12:05.000Z"
}
EventoQué hacer
conversion.completedMarca la conversión como hecha. targetAmountUsdt ya está en tu saldo USDT y puedes enviarlo.
conversion.createdEmpezó una conversión: aceptaste una cotización, o la conversión automática convirtió parte de un pago COP (origin: "auto", paymentId). El origen ya está debitado.
conversion.canceledMárcala como cancelada. sourceAmount volvió a tus VES o COP disponibles. cancelReason es canceled_by_tenant si la cancelaste tú; si no, el motivo por el que VEXPay no pudo completarla. Cotiza de nuevo si aún quieres USDT.

Cada evento se entrega una vez y no se reintenta, así que concilia también: lista tus conversiones pendientes (GET /v1/conversions?status=PENDING) de forma periódica y consulta las que no viste terminar (GET /v1/conversions/{id}). Procesa los eventos de forma idempotente por id.

¿Cambiaste de idea? Cancela una conversión pendiente con POST /v1/conversions/{id}/cancel: los VES vuelven al instante. Una vez COMPLETED o CANCELED ya no se puede cancelar (409 conversion_not_cancelable).

Ejemplo completo

Convierte una parte de los VES del día, guarda la conversión y termínala desde el webhook.

Node (@vexpay/node)javascript
import VexPay from '@vexpay/node';

const vexpay = new VexPay(process.env.VEXPAY_API_KEY);

// 1. Convertir (p. ej. desde un job diario)
export async function convertVes(amountVes, runId) {
  const quote = await vexpay.conversions.quotes.create({ sourceAmountVes: amountVes });
  console.log(`${quote.sourceAmountVes} VES → ${quote.targetAmountUsdt} USDT a ${quote.rate}`);

  const conversion = await vexpay.conversions.create(
    { quoteId: quote.id, reference: runId },
    { idempotencyKey: `convert-${runId}` },
  );
  await db.conversions.insert({ id: conversion.id, status: conversion.status, usdt: conversion.targetAmountUsdt });
}

// 2. Terminar desde el webhook
app.post('/webhooks/vexpay', express.raw({ type: 'application/json' }), async (req, res) => {
  const event = vexpay.webhooks.constructEvent(req.body, req.headers['vexpay-signature'], process.env.VEXPAY_WEBHOOK_SECRET);
  if (event.event === 'conversion.completed' || event.event === 'conversion.canceled') {
    await db.conversions.update(event.data.id, { status: event.data.status });
  }
  res.sendStatus(200);
});

// 3. Conciliar (p. ej. cada hora)
for await (const c of vexpay.conversions.list({ status: 'PENDING' })) {
  await db.conversions.update(c.id, { status: c.status });
}
Python (vexpay)python
from vexpay import VexPay, Webhook

vexpay = VexPay(os.environ["VEXPAY_API_KEY"])

def convert_ves(amount_ves: str, run_id: str):
    quote = vexpay.conversions.quotes.create({"sourceAmountVes": amount_ves})
    conversion = vexpay.conversions.create(
        {"quoteId": quote.id, "reference": run_id},
        idempotency_key=f"convert-{run_id}",
    )
    db.save_conversion(conversion.id, conversion.status, conversion.targetAmountUsdt)

def on_webhook(raw_body: bytes, signature: str):
    event = Webhook.construct_event(raw_body, signature, os.environ["VEXPAY_WEBHOOK_SECRET"])
    if event.event in ("conversion.completed", "conversion.canceled"):
        db.update_conversion(event.data["id"], event.data["status"])

Después de convertir: usa tus USDT

  • Los USDT están en tu saldo USDT de VEXPay (GET /v1/crypto/balance → availableUsdt), junto con los USDT que recibas de tus clientes.
  • Envíalos a cualquier wallet con POST /v1/crypto/payouts (aplica la comisión de red; ver USDT y USDC). Los payouts dentro de tus límites salen automáticamente; los más grandes se revisan primero.
  • Una conversión nunca envía USDT a una wallet por sí sola, y los USDT nunca se convierten de vuelta a VES.

Buenas prácticas

  • Cotiza justo antes de convertir. Con 410 quote_expired, pide una nueva cotización y muestra los montos nuevos: nunca aceptes una tasa que el usuario no vio.
  • Usa una Idempotency-Key por cada conversión que quieras hacer (p. ej. tu id de ejecución u orden) y repítela en los reintentos.
  • Con 422 insufficient_balance, lee availableVes en el error y cotiza un monto menor; payouts en VES u otras conversiones pueden haber usado el saldo.
  • Considera tuyos los USDT solo después de conversion.completed.
  • Usa un reference con sentido: es la forma más fácil de cuadrar conversiones con tu contabilidad.

Errores

EstadoerrorSignificado
400invalid_amountEnvía exactamente un monto positivo con hasta 2 decimales (COP: pesos enteros).
400invalid_datasourceCurrency desconocido, o un campo de configuración que no puedes cambiar.
403conversions_not_enabledLas conversiones (o USDT, o COP para conversiones COP) no están habilitadas en tu cuenta.
403live_mode_not_activatedTu cuenta de producción aún no está aprobada; usa modo prueba.
404not_foundCotización o conversión desconocida (o de otra cuenta).
409quote_usedEsa cotización ya fue aceptada.
409conversion_not_cancelableSolo se puede cancelar una conversión PENDING.
410quote_expiredPasaron más de 60 segundos: pide una nueva cotización.
422amount_below_minimumPor debajo de tu mínimo de USDT por conversión.
422insufficient_balanceTu saldo disponible en VES no cubre el monto.
422daily_limit_exceededLa conversión superaría tu límite diario en VES.
503rate_unavailableNo hay tasa de mercado actual; reintenta en breve.

Pruebas

Las conversiones están activas en tu cuenta de prueba siempre que USDT esté habilitado. Con una clave de prueba, las conversiones se completan al instante: se debitan los VES de prueba, se acredita tu saldo USDT de prueba y se envía conversion.completed, para que construyas y pruebes todo el flujo (incluido tu manejador de webhooks) antes de salir a producción. Usan la tasa de mercado real pero nunca mueven dinero real.

Preguntas frecuentes

PreguntaRespuesta
¿La tasa es la del BCV?No. Es la tasa de mercado USDT/VES actual más tu spread.
¿Puede cambiar el monto después de aceptar?No. Ambos montos quedan fijos al aceptar la cotización.
¿Hay otras comisiones?No. El spread es el precio de la conversión. Enviar los USDT después cuesta la comisión de red del payout.
¿Puedo convertir USDT de vuelta a VES?No. Las conversiones solo van de VES o COP a USDT; no hay off-ramp de USDT a VES. Los pagos por Pago Móvil o transferencia (POST /v1/payouts) salen solo de tu saldo en VES, que viene de los bolívares que cobras, así que los USDT no pueden financiarlos. Si necesitas un off-ramp, contacta a VEX Pay.
¿VEXPay puede enviar los USDT directo a mi wallet?No como parte de la conversión. Cuando estén en tu saldo USDT, crea un payout USDT.
¿Cómo lo habilito o cambio mi spread o límites?Contacta a VEX Pay.