Quickstart
Esta guía te lleva desde cero hasta tu primera transacción en producción.
Paso 1 — Solicita tu cuenta
Sección titulada «Paso 1 — Solicita tu cuenta»Escribe al equipo de Rivopay para recibir:
- Tu correo de acceso a la plataforma
- Tu contraseña temporal
Paso 2 — Generar tu par de claves EC secp256k1
Sección titulada «Paso 2 — Generar tu par de claves EC secp256k1»# Generar clave privada EC secp256k1openssl ecparam -name secp256k1 -genkey -noout -out private.pem
# Extraer la clave públicaopenssl ec -in private.pem -pubout -out public.pemEnvía el contenido de public.pem al equipo de Rivopay. Ellos te devolverán tu keyId (formato rivo_xxxxxxxxxx). Guárdalo — lo usarás en cada request autenticado.
Paso 3 — Configurar tu webhook URL
Sección titulada «Paso 3 — Configurar tu webhook URL»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.
Paso 4 — Consultar tu balance
Sección titulada «Paso 4 — Consultar tu balance»Antes de operar, verifica que tu cuenta tiene saldo disponible.
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.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/br/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);Respuesta esperada:
{ "available": 150000, "currency": "BRL", "amountFormat": "cents"}| Campo | Tipo | Descripción |
|---|---|---|
available | number | Saldo disponible en centavos |
currency | string | Moneda (BRL) |
amountFormat | string | Formato del monto (cents) |
Paso 5 — Crear tu primer Pay-In
Sección titulada «Paso 5 — Crear tu primer Pay-In»async function createPayIn(amount, description, txIdSource) { const path = '/v1/br/payin/pix/instant'; const body = { amount, amountFormat: 'cents', amountType: 'fixed', expirationInSeconds: 3600, description, txIdSource, }; const bodyString = JSON.stringify(body);
const response = await fetch(`${BASE_URL}${path}`, { method: 'POST', headers: buildAuthHeaders('POST', path, '', bodyString), body: bodyString, });
return response.json();}
const result = await createPayIn(10000, 'Pedido #1234', 'ORD-20260318-001');console.log('QR generado:', result.txId, result.copiaECola);Paso 6 — Crear tu primer Pay-Out
Sección titulada «Paso 6 — Crear tu primer Pay-Out»async function createPayOut(amount, pixKeyType, pixKeyValue) { const path = '/v1/br/payout/pix/key'; const body = { pixKeyType, pixKeyValue, amount }; 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); return null; }
return result;}
const payout = await createPayOut(5000, 'CPF', '123.456.789-00');// El resultado final llega via webhook: payout.completed o payout.failedPaso 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/br/payin/pix/${txId}`;
const response = await fetch(`${BASE_URL}${path}`, { method: 'GET', headers: buildAuthHeaders('GET', path), });
return response.json();}Paso 8 — Flujo completo recomendado
Sección titulada «Paso 8 — Flujo completo recomendado»Cliente en tu plataforma quiere pagar │ ▼POST /v1/br/payin/pix/instant → Guarda txId en tu base de datos → Muestra qrCode o copiaECola al cliente │ ▼Cliente paga con su app de banco │ ▼Webhook → payin.completed (header X-Webhook-Event) → payload incluye debtor (quien pagó) y creditor → Acredita el pedido en tu sistema → Notifica al cliente: "pago recibido" │ (si no llega webhook) │ ▼GET /v1/br/payin/pix/{txId} → Consulta activa tras 5+ min en PENDING → Si sigue PENDING, el QR puede expirarPaso 9 — Manejo de errores y reintentos
Sección titulada «Paso 9 — Manejo de errores y reintentos»async function createPayInWithRetry(amount, description, txIdSource, maxRetries = 3) { const idempotencyKey = crypto.randomUUID();
for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const path = '/v1/br/payin/pix/instant'; const body = { amount, amountFormat: 'cents', amountType: 'fixed', expirationInSeconds: 3600, description, txIdSource }; 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));}