Quickstart
Esta guía te lleva desde cero hasta tu primera transacción en producción.
Paso 1 — Obtener credenciales
Sección titulada «Paso 1 — Obtener credenciales»Contacta al equipo de Rivopay para recibir:
- Tu
clientId - Tu
keyId(formatorivo_xxxxxxxxxx) - Instrucciones para registrar tu clave pública EC secp256k1
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 a Rivopay.
Paso 3 — Configurar tu webhookUrl
Sección titulada «Paso 3 — Configurar tu webhookUrl»Crea un endpoint en tu servidor que reciba POST requests:
app.post('/webhook/rivoplay', express.json(), async (req, res) => { const eventType = req.headers['x-webhook-event']; const payload = req.body; const { txId, status } = payload;
switch (eventType) { case 'payin.completed': await confirmarPedido(txId, payload.netAmount); break; case 'payin.failed': await cancelarPedido(txId); break; case 'payout.failed': await notificarFalloPago(txId, payload.failureReason); break; case 'payout.reversed': await procesarReversion(txId); break; }
res.status(200).json({ received: true });});Paso 4 — Crear tu primer Pay-In
Sección titulada «Paso 4 — Crear tu primer Pay-In»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.rivoplay.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 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 5 — Crear tu primer Pay-Out
Sección titulada «Paso 5 — 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 6 — Consultar el estado de una transacción
Sección titulada «Paso 6 — 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 7 — Flujo completo recomendado
Sección titulada «Paso 7 — 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 8 — Manejo de errores y reintentos
Sección titulada «Paso 8 — 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));}