Ir al contenido

Quickstart

Esta guía te lleva desde cero hasta tu primera transacción en producción con Bre-B (Colombia).

Escribe al equipo de Rivopay para recibir:

  • Tu correo de acceso a la plataforma
  • Tu contraseña temporal

Tu comercio debe estar vinculado en Bre-B (customer + cuenta) por nuestro equipo antes de cobrar.

Paso 2 — Generar tu par de claves EC secp256k1

Sección titulada «Paso 2 — Generar tu par de claves EC secp256k1»
Ventana de terminal
# Generar clave privada EC secp256k1
openssl ecparam -name secp256k1 -genkey -noout -out private.pem
# Extraer la clave pública
openssl ec -in private.pem -pubout -out public.pem

Envía el contenido de public.pem al equipo de Rivopay. Ellos te devolverán tu keyId (formato rivo_xxxxxxxxxx) creada con region: "CO". Guárdalo — lo usarás en cada request autenticado.

En la plataforma, ve a Mi cuenta → Webhook y registra la URL de tu servidor donde Rivopay enviará las notificaciones de eventos.

Guarda esa URL — la necesitarás activa antes de procesar transacciones reales.

Antes de operar, verifica que tu cuenta tiene saldo disponible. Los montos van en pesos COP enteros.

const crypto = require('crypto');
const fs = require('fs');
const PRIVATE_KEY = fs.readFileSync('./private.pem', 'utf8');
const KEY_ID = 'rivo_abc123xyz';
const BASE_URL = 'https://api.sandbox.rivopaylatam.com';
function buildAuthHeaders(method, path, query = '', bodyString = '') {
const timestamp = new Date().toISOString();
const nonce = crypto.randomBytes(16).toString('hex');
const signedData = JSON.stringify({ method, path, query, body: bodyString, timestamp, nonce });
const signature = crypto.sign('SHA256', Buffer.from(signedData), PRIVATE_KEY).toString('base64');
return {
'Content-Type': 'application/json',
'x-client-id': KEY_ID,
'x-timestamp': timestamp,
'x-nonce': nonce,
'x-signature': signature,
};
}
async function getBalance() {
const path = '/v1/co/balance';
const response = await fetch(`${BASE_URL}${path}`, {
method: 'GET',
headers: buildAuthHeaders('GET', path),
});
return response.json();
}
const balance = await getBalance();
console.log('Balance disponible:', balance.availableBalance);

Respuesta esperada:

{
"country": "CO",
"currency": "COP",
"totalBalance": 1250000,
"holdBalance": 30800,
"availableBalance": 1219200
}
CampoTipoDescripción
availableBalancenumberDisponible para dispersar, en pesos COP enteros
holdBalancenumberRetenido por dispersiones en curso
currencystringMoneda (COP)

Paso 5 — Crear tu primer Pay-In (QR Bre-B)

Sección titulada «Paso 5 — Crear tu primer Pay-In (QR Bre-B)»
async function createPayIn(amount, reference) {
const path = '/v1/co/breb/payin/qr';
const body = {
type: 'DYNAMIC',
channel: 'ECOMM',
amount,
reference,
};
const bodyString = JSON.stringify(body);
const response = await fetch(`${BASE_URL}${path}`, {
method: 'POST',
headers: buildAuthHeaders('POST', path, '', bodyString),
body: bodyString,
});
return response.json();
}
// amount en pesos COP enteros (máximo 11.500.000 por transacción)
const result = await createPayIn(50000, 'orden-8842');
console.log('QR generado:', result.txId, result.qrCode);
// Muestra result.qrImage (data URI PNG) o renderiza result.qrCode (EMVCo)

Paso 6 — Crear tu primer Pay-Out (dispersión Bre-B)

Sección titulada «Paso 6 — Crear tu primer Pay-Out (dispersión Bre-B)»
async function createPayOut(amount, keyType, keyValue, reference) {
const path = '/v1/co/breb/payout';
const body = { keyType, keyValue, amount, reference };
const bodyString = JSON.stringify(body);
const response = await fetch(`${BASE_URL}${path}`, {
method: 'POST',
headers: buildAuthHeaders('POST', path, '', bodyString),
body: bodyString,
});
const result = await response.json();
if (response.status === 402) {
console.error('Saldo insuficiente:', result.availableBalance, 'requerido:', result.requiredAmount);
return null;
}
return result;
}
// Dispersión a una llave PHONE (celular). El resultado final llega vía webhook.
const payout = await createPayOut(30000, 'PHONE', '3009876543', 'retiro-551');
console.log('Dispersión:', payout.externalId, payout.status);

Paso 7 — Consultar el estado de una transacción

Sección titulada «Paso 7 — Consultar el estado de una transacción»
async function getPayInStatus(txId) {
const path = `/v1/co/breb/payin/qr/${txId}`;
const response = await fetch(`${BASE_URL}${path}`, {
method: 'GET',
headers: buildAuthHeaders('GET', path),
});
return response.json();
}
Cliente en tu plataforma quiere pagar
POST /v1/co/breb/payin/qr
→ Guarda txId en tu base de datos
→ Muestra qrImage o renderiza qrCode (EMVCo) al cliente
Cliente paga desde cualquier app del ecosistema Bre-B
Webhook → payin.completed (header X-Webhook-Event)
→ payload incluye netAmount y e2eId
→ Acredita el pedido en tu sistema
→ Notifica al cliente: "pago recibido"
(si no llega webhook)
GET /v1/co/breb/payin/qr/{txId}
→ Consulta activa; si sigue PENDING, sincroniza contra la red
→ El QR expira a los 15 minutos
async function createPayInWithRetry(amount, reference, maxRetries = 3) {
const idempotencyKey = crypto.randomUUID();
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const path = '/v1/co/breb/payin/qr';
const body = {
type: 'DYNAMIC',
channel: 'ECOMM',
amount,
reference,
};
const bodyString = JSON.stringify(body);
const response = await fetch(`${BASE_URL}${path}`, {
method: 'POST',
headers: {
...buildAuthHeaders('POST', path, '', bodyString),
'x-idempotency-key': idempotencyKey,
},
body: bodyString,
});
if (response.status === 502 || response.status === 503) {
await sleep(Math.pow(2, attempt) * 1000);
continue;
}
return response.json();
} catch (err) {
if (attempt === maxRetries) throw err;
await sleep(Math.pow(2, attempt) * 1000);
}
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}