Cobra en pesos colombianos
Cobra a compradores en Colombia con tarjeta, PSE, Bancolombia, 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/paymentseligiendo unchannel:pse,bancolombia_transfer,bancolombia_qr,breb,nequiodaviplata. Las tarjetas (card) se cobran solo en el checkout alojado. Los montos son pesos enteros (amountCop), mínimo COP 20.000 por pago; un monto menor responde 400amount_below_minimumconminimumCop. GET /v1/cop/channelslista los canales que tu cuenta puede usar ahora, los bancos PSE y los enlaces de términos que piden algunos canales. Arma tu selector de canales con esa respuesta.- La respuesta indica qué hace el comprador (
next.type). El pago se completa de forma asíncrona: escuchapayment.completed/payment.failedo consúltalo conGET /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/balancereportaavailableCop,heldCopynextReleaseAt. Ver Saldos, retenciones y payouts. - Los retiros en COP se envían por transferencia bancaria en Colombia; solicítalos a VEX Pay. Recibes
payout.completedconcurrency: "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
| channel | El comprador | Datos del comprador | Reembolso |
|---|---|---|---|
card | Paga con Visa o Mastercard en el checkout alojado (1 cuota) | Los pide el checkout | Total, dentro de 24 horas |
pse | Va a su banco y autoriza el débito | buyer + firstName, lastName, personType; pse.bankCode | No por la API |
bancolombia_transfer | Va a Bancolombia y autoriza la transferencia | buyer | No por la API |
bancolombia_qr | Escanea un QR con la app Bancolombia | buyer | No por la API |
breb | Escanea el QR o ingresa la llave en cualquier app bancaria colombiana (~15 min) | Opcionales | Total, dentro de 96 horas |
nequi | Aprueba la notificación push en la app Nequi (~5 min) | buyer | Depende de tu cuenta |
daviplata | Escribe un código SMS, o autoriza en una página de Daviplata (sigue next.type) | buyer; documentType CC, CE o TI | Depende de tu cuenta |
buyer significa email, phone, documentType y documentNumber. buyer.phone es un celular colombiano (10 dígitos que empiezan por 3; +57 opcional). documentType es uno de CC, CE, TI, NIT, PP. personType (PSE) es natural o juridica.
Los canales cuya entrada del catálogo tiene needsTermsAcceptance: true piden que el comprador acepte los términos de la red de pagos: muestra los dos enlaces de terms junto a una casilla y envía buyer.termsAccepted: true. Sin eso el pago responde 400 invalid_data indicando buyer.termsAccepted.
curl "${API_URL}/v1/cop/channels" \
--header "x-api-key: ${VEXPAY_API_KEY}"{
"channels": [
{ "channel": "pse", "label": "PSE", "needsBuyer": true, "needsTermsAcceptance": true, "checkoutOnly": false, "minAmountCop": 20000 },
{ "channel": "card", "label": "Tarjeta", "needsBuyer": true, "needsTermsAcceptance": true, "checkoutOnly": true, "minAmountCop": 20000 },
{ "channel": "breb", "label": "Bre-B", "needsBuyer": false, "needsTermsAcceptance": false, "checkoutOnly": false, "minAmountCop": 20000 }
],
"pseBanks": [{ "code": "1007", "name": "Bancolombia" }],
"terms": { "termsUrl": "https://…", "personalDataUrl": "https://…" }
}Precios
| Movimiento | Comisión |
|---|---|
| Pago COP completado | 4,5 % del monto + COP 1.500, en pesos (nunca más que el monto) |
| Reembolso total | La 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 tarjeta, PSE, Bancolombia, 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.
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.
- Las tarjetas solo se cobran aquí: el número de la tarjeta va del navegador del comprador directo a la red de pagos, nunca a ti ni a VEX Pay. Si la tarjeta es rechazada, el comprador puede probar otra tarjeta u otro canal.
- PSE y Bancolombia llevan al comprador a su banco y lo regresan al mismo checkout, que retoma el pago y muestra el resultado.
- 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ásamountUsdycopRate, y el bloque habitualcheckoutSession(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.
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" }'const payment = await vexpay.cop.payments.create(
{ amountCop: 100000, channel: 'breb', reference: 'order-1042' },
{ idempotencyKey: 'order-1042' },
);
// payment.next.qrPngBase64, payment.next.transferKeypayment = vexpay.cop.payments.create(
{"amountCop": 100000, "channel": "breb", "reference": "order-1042"},
idempotency_key="order-1042",
)$payment = $vexpay->cop->payments->create(
['amountCop' => 100000, 'channel' => 'breb', 'reference' => 'order-1042'],
['idempotency_key' => 'order-1042'],
);{
"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)
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", "termsAccepted": true }
}'await vexpay.cop.payments.create({
amountCop: 50000,
channel: 'nequi',
buyer: { email: 'juan@example.com', phone: '3001234567', documentType: 'CC', documentNumber: '1234567890', termsAccepted: true },
});vexpay.cop.payments.create({
"amountCop": 50000,
"channel": "nequi",
"buyer": {"email": "juan@example.com", "phone": "3001234567", "documentType": "CC", "documentNumber": "1234567890", "termsAccepted": True},
})$vexpay->cop->payments->create([
'amountCop' => 50000,
'channel' => 'nequi',
'buyer' => ['email' => 'juan@example.com', 'phone' => '3001234567', 'documentType' => 'CC', 'documentNumber' => '1234567890', 'termsAccepted' => true],
]);La respuesta trae next: { "type": "approve_in_app", "phoneMasked": "****567" }. Pídele al comprador que abra Nequi y apruebe. Envía termsAccepted: true cuando el catálogo de canales diga needsTermsAcceptance (si no, se ignora).
Paso 03
Daviplata (código SMS)
Crea el pago con channel: "daviplata" y un buyer. Con next.type: "submit_otp", 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. Algunas cuentas responden next.type: "redirect": envía al comprador a next.url como en PSE (el endpoint del OTP responde entonces 409).
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" }'await vexpay.cop.payments.submitOtp(payment.id, { otp: '123456' });vexpay.cop.payments.submit_otp(payment.id, {"otp": "123456"})$vexpay->cop->payments->submitOtp($payment->id, ['otp' => '123456']);Paso 04
PSE y Bancolombia (redirección al banco)
Crea el pago y envía al comprador a next.url (next.type: "redirect"). La URL del banco puede tardar unos segundos en existir: si next.type es redirect_pending, vuelve a consultar el pago (GET /v1/cop/payments/:id) hasta que traiga la redirección. Tras autorizar, el comprador llega a una página de VEX Pay que le dice que puede cerrar la pestaña; tú recibes el resultado por webhook.
curl -X POST "${API_URL}/v1/cop/payments" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--header "content-type: application/json" \
--header "Idempotency-Key: order-1043" \
--data '{
"amountCop": 80000,
"channel": "pse",
"pse": { "bankCode": "1007" },
"buyer": {
"firstName": "Juan", "lastName": "Pérez", "personType": "natural",
"email": "juan@example.com", "phone": "3001234567",
"documentType": "CC", "documentNumber": "1234567890",
"termsAccepted": true
}
}'{
"id": "9a2e…",
"status": "pending",
"method": "COP",
"channel": "pse",
"amountCop": 80000,
"next": { "type": "redirect", "url": "https://…" }
}bancolombia_transfer funciona igual, solo con buyer. bancolombia_qr responde next: { "type": "show_qr", "qrSvgBase64": "…" }: muéstralo con <img src="data:image/svg+xml;base64,{qrSvgBase64}"> y espera el webhook.
Estados, cancelación y reembolsos
| status | Significado |
|---|---|
pending | Esperando al comprador. |
completed | Pagado y acreditado a tu saldo COP (feeCop presente). |
failed | failureCode: "expired" (no pagó a tiempo) o "rejected" (lo rechazó el comprador, el banco o el emisor de la tarjeta). |
canceled | Lo cancelaste con POST /v1/cop/payments/:id/cancel antes de que se pagara. |
refunded | Reembolsado por completo con POST /v1/cop/payments/:id/refund, o un contracargo de tarjeta (failureCode: "chargeback"). |
La cancelación funciona mientras el pago está pendiente. En los canales con redirección al banco (PSE, Bancolombia) la cancelación queda registrada en VEX Pay; si el banco lo aprueba igual, VEX Pay lo revisa y le devuelve el dinero al comprador o te lo acredita.
Los reembolsos son solo totales y dependen del canal (ver la tabla de Canales): tarjetas dentro de 24 horas, Bre-B dentro de 96 horas (después: 409 refund_window_expired). Un canal que no se puede reembolsar por la API responde 409 refund_not_supported; contacta a VEX Pay para devolverle el dinero al comprador. 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, o reason: "chargeback" cuando el comprador disputa un cobro con tarjeta ante su banco — el monto se descuenta de tu saldo COP). El payload incluye method: "COP", channel, amountCop, feeCop, reference y metadata. Los pagos con tarjeta (en webhooks y en GET /v1/cop/payments/:id) traen además card: { brand, last4 }.
{
"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:
| Canal | Datos del comprador | Qué pasa |
|---|---|---|
breb | Ninguno | El sandbox devuelve un QR y una llave. Confirma consultando GET /v1/cop/payments/:id o por webhook. |
nequi | Teléfono 3991111111, más cualquier documento y correo válidos | Se completa solo en alrededor de un minuto. 3992222222 se rechaza. |
daviplata | Igual que Nequi; documentType solo CC, CE o TI | Con next.type: "submit_otp", el OTP 123456, 000000 o 111111 lo completa; cualquier otro código responde 400 invalid_otp. Con redirect, abre next.url y sigue la página de prueba. |
card (checkout) | Tarjeta 4242 4242 4242 4242, cualquier vencimiento futuro y CVC | Aprobada. 4111 1111 1111 1111 se rechaza. |
pse | Cualquier dato válido del comprador | El banco con código 1 aprueba, 2 rechaza. |
bancolombia_transfer | Cualquier dato válido del comprador | Abre next.url y elige el resultado en la página de prueba. |
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.