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

Cobra en pesos colombianos

Cobra a compradores en Colombia con Bre-B, Nequi o Daviplata. Los pagos se liquidan en pesos colombianos en un saldo COP propio — nunca se convierten a bolívares ni a USD.

Cómo funciona COP

  • Crea un pago con POST /v1/cop/payments eligiendo un channel: breb, nequi o daviplata. Los montos son pesos enteros (amountCop), mínimo COP 20.000 por pago; un monto menor responde 400 amount_below_minimum con minimumCop.
  • La respuesta indica qué hace el comprador (next.type). El pago se completa de forma asíncrona: escucha payment.completed / payment.failed o consúltalo con GET /v1/cop/payments/:id.
  • Los pagos completados se acreditan a tu saldo COP (GET /v1/cop/balance) descontando la comisión COP. COP se mantiene separado de tus saldos en VES y stablecoins.
  • Los pesos nuevos quedan retenidos 24 horas: GET /v1/cop/balance reporta availableCop, heldCop y nextReleaseAt. Ver Saldos, retenciones y payouts.
  • Los retiros en COP se envían por transferencia bancaria en Colombia; solicítalos a VEX Pay. Recibes payout.completed con currency: "COP" cuando se paga.

Los SDKs de Node (vexpay.cop), Python (vexpay.cop), PHP ($vexpay->cop) y Laravel (VexPay::cop()) cubren todos los endpoints de COP: payments.create, retrieve, submitOtp, cancel, refund y balance.retrieve.

Canales

channelEl compradorDatos del compradorVence
brebEscanea el QR o ingresa la llave en cualquier app bancaria colombianaOpcionales~15 min
nequiAprueba la notificación push en la app Nequibuyer obligatorio (email, phone, documentType, documentNumber)~5 min
daviplataTe dicta el código que recibe por SMS y tú lo envíasbuyer obligatorio; documentType CC, CE o TI~5 min

buyer.phone es un celular colombiano (10 dígitos que empiezan por 3; +57 opcional). documentType es uno de CC, CE, TI, NIT, PP.

Precios

MovimientoComisión
Pago COP completado4,5 % del monto + COP 1.500, en pesos (nunca más que el monto)
Reembolso totalLa comisión se devuelve con el reembolso

Ejemplo: un comprador paga 100.000 COP → comisión 6.000 (4.500 + 1.500) → se acreditan 94.000 COP. El pago mínimo es de 20.000 COP.

Checkout alojado

Los links de pago y las sesiones de checkout pueden cobrar en COP sin que construyas ninguna pantalla. La página alojada deja al comprador elegir la moneda (bolívares, cripto o pesos colombianos), muestra el total en cada una y ejecuta el paso de Bre-B, Nequi o Daviplata. Agrega cop a methods al crear la sesión, o deja methods vacío: COP se ofrece siempre que esté habilitado en tu cuenta.

curlbash
curl -X POST "${API_URL}/v1/checkout/sessions" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --header "content-type: application/json" \
  --data '{ "amountUsd": 25.00, "methods": ["c2p", "usdt", "cop"], "reference": "order-1042" }'
  • La sesión tiene precio en USD. El monto en pesos es el monto en USD × la tasa USDT/COP de VEX Pay más un margen cambiario de 2,5 %, redondeado hacia arriba a pesos enteros, y queda fijo cuando el comprador inicia el pago.
  • Si no hay una tasa vigente, la página oculta la opción en pesos; bolívares y cripto siguen funcionando.
  • Los links de vendedores de marketplace (merchants) también ofrecen COP: el pago se acredita al saldo COP de ese vendedor, descontando la comisión COP, cuando termina la más tardía entre tu retención de vendedores y la retención de 24 h del saldo.
  • COP se oculta cuando el total en pesos es menor al mínimo de COP 20.000 (en un link donde el comprador elige la cantidad, se muestra deshabilitado hasta que el total lo alcance).
  • Aplica la comisión COP como en cualquier pago COP. Los webhooks payment.* de pagos de checkout incluyen además amountUsd y copRate, y el bloque habitual checkoutSession (id, reference, metadata).

Paso 01

Bre-B (QR o llave)

Muestra la imagen QR (next.qrPngBase64, PNG en base64) y la llave (next.transferKey) para copiar. Solo se devuelven en esta respuesta.

curlbash
curl -X POST "${API_URL}/v1/cop/payments" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --header "content-type: application/json" \
  --header "Idempotency-Key: order-1042" \
  --data '{ "amountCop": 100000, "channel": "breb", "reference": "order-1042" }'
Node SDKjavascript
const payment = await vexpay.cop.payments.create(
  { amountCop: 100000, channel: 'breb', reference: 'order-1042' },
  { idempotencyKey: 'order-1042' },
);
// payment.next.qrPngBase64, payment.next.transferKey
Python SDKpython
payment = vexpay.cop.payments.create(
    {"amountCop": 100000, "channel": "breb", "reference": "order-1042"},
    idempotency_key="order-1042",
)
PHP SDKphp
$payment = $vexpay->cop->payments->create(
    ['amountCop' => 100000, 'channel' => 'breb', 'reference' => 'order-1042'],
    ['idempotency_key' => 'order-1042'],
);
Respuestajson
{
  "id": "6f1c…",
  "status": "pending",
  "method": "COP",
  "channel": "breb",
  "amountCop": 100000,
  "reference": "order-1042",
  "metadata": {},
  "expiresAt": "2026-10-07T20:15:00.000Z",
  "createdAt": "2026-10-07T20:00:00.000Z",
  "next": { "type": "show_qr", "qrPngBase64": "iVBORw0KGgo…", "transferKey": "BREB-…" }
}

Muestra el QR con <img src="data:image/png;base64,{qrPngBase64}">.

Paso 02

Nequi (push)

curlbash
curl -X POST "${API_URL}/v1/cop/payments" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --header "content-type: application/json" \
  --data '{
    "amountCop": 50000,
    "channel": "nequi",
    "buyer": { "email": "juan@example.com", "phone": "3001234567", "documentType": "CC", "documentNumber": "1234567890" }
  }'
Node SDKjavascript
await vexpay.cop.payments.create({
  amountCop: 50000,
  channel: 'nequi',
  buyer: { email: 'juan@example.com', phone: '3001234567', documentType: 'CC', documentNumber: '1234567890' },
});
Python SDKpython
vexpay.cop.payments.create({
    "amountCop": 50000,
    "channel": "nequi",
    "buyer": {"email": "juan@example.com", "phone": "3001234567", "documentType": "CC", "documentNumber": "1234567890"},
})
PHP SDKphp
$vexpay->cop->payments->create([
    'amountCop' => 50000,
    'channel' => 'nequi',
    'buyer' => ['email' => 'juan@example.com', 'phone' => '3001234567', 'documentType' => 'CC', 'documentNumber' => '1234567890'],
]);

La respuesta trae next: { "type": "approve_in_app", "phoneMasked": "****567" }. Pídele al comprador que abra Nequi y apruebe.

Paso 03

Daviplata (código SMS)

Crea el pago con channel: "daviplata" y un buyer. El comprador recibe un código por SMS; envíalo para completar el pago. Un código incorrecto responde 400 invalid_otp y el pago sigue pendiente hasta vencer.

curlbash
curl -X POST "${API_URL}/v1/cop/payments/{id}/otp" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --header "content-type: application/json" \
  --data '{ "otp": "123456" }'
Node SDKjavascript
await vexpay.cop.payments.submitOtp(payment.id, { otp: '123456' });
Python SDKpython
vexpay.cop.payments.submit_otp(payment.id, {"otp": "123456"})
PHP SDKphp
$vexpay->cop->payments->submitOtp($payment->id, ['otp' => '123456']);

Estados, cancelación y reembolsos

statusSignificado
pendingEsperando al comprador.
completedPagado y acreditado a tu saldo COP (feeCop presente).
failedfailureCode: "expired" (no pagó a tiempo) o "rejected" (el comprador lo rechazó).
canceledLo cancelaste con POST /v1/cop/payments/:id/cancel antes de que se pagara.
refundedReembolsado por completo con POST /v1/cop/payments/:id/refund.

Los reembolsos son solo totales y dentro de las 96 horas siguientes al pago (después: 409 refund_window_expired). Lo acreditado se descuenta de tu saldo COP (una venta que sigue en la retención de 24 h se toma de lo retenido) y la comisión se devuelve; si los pesos disponibles más los retenidos no alcanzan, la respuesta es 409 insufficient_balance. Poco después del pago un reembolso también puede responder 503 processor_unavailable (no disponible temporalmente): no se descuenta nada; reintenta más tarde.

Webhooks

Los pagos COP envían payment.completed, payment.failed, payment.canceled y payment.reversed (reembolso). El payload incluye method: "COP", channel, amountCop, feeCop, reference y metadata.

payment.completedjson
{
  "event": "payment.completed",
  "data": {
    "paymentId": "6f1c…",
    "status": "completed",
    "method": "COP",
    "currency": "COP",
    "channel": "nequi",
    "amountCop": 100000,
    "feeCop": 6000,
    "reference": "order-1042",
    "metadata": {},
    "livemode": true
  }
}

Modo de prueba

Las llaves de prueba (y los tenants en modo de prueba) envían los pagos COP al sandbox del procesador. Usa estos valores:

CanalDatos del compradorQué pasa
brebNingunoEl sandbox devuelve un QR y una llave. Confirma consultando GET /v1/cop/payments/:id o por webhook.
nequiCualquier dato válido, p. ej. teléfono 3001234567, CC 1234567890, correo juan@example.comSe completa solo, alrededor de un minuto después de crearlo.
daviplataIgual que Nequi; documentType solo CC, CE o TIEl OTP 123456, 000000 o 111111 lo completa; cualquier otro código responde 400 invalid_otp.

En el sandbox los documentos y teléfonos solo se validan por formato: phone son 10 dígitos que empiezan por 3 (+57 opcional), documentNumber de 3 a 20 letras, dígitos o guiones.