¿Qué vas a lograr?
Al terminar esta guía tendrás un servidor que recibe y valida webhooks de OnePay, permitiéndote reaccionar en tiempo real a eventos como pagos aprobados, cargos exitosos o dispersiones completadas.
Prerrequisitos
- Cuenta de OnePay con llaves API
- Un servidor web con una URL pública accesible (o Ngrok para desarrollo local)
¿Cómo funcionan los webhooks?
OnePay envía una solicitud HTTP POST a tu URL con:
- El payload del evento en el body
- Un header
x-webhook-token con un token de autenticación
- Una firma HMAC-SHA256 para verificar la integridad
Paso a paso
Configurar la URL del webhook
- Ve a Desarrolladores > Webhooks en el panel de OnePay
- Agrega la URL de tu servidor (ej:
https://tuapp.com/webhooks/onepay)
- Copia el secreto generado y el token de autenticación - los necesitarás para verificar los webhooks
Crear el endpoint en tu servidor
Tu servidor debe:
- Recibir solicitudes POST
- Verificar la firma HMAC
- Responder
200 OK inmediatamente
- Procesar el evento de forma asíncrona
Node.js (Express)
Python (Flask)
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = 'wh_tok_TU_SECRETO';
const WEBHOOK_TOKEN = 'wh_hdr_TU_TOKEN';
app.post('/webhooks/onepay', (req, res) => {
// 1. Verificar el token de autenticación
const token = req.headers['x-webhook-token'];
if (token !== WEBHOOK_TOKEN) {
return res.status(401).send('Token inválido');
}
// 2. Verificar la firma HMAC
const payload = JSON.stringify(req.body);
const signature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');
// 3. Responder 200 inmediatamente
res.status(200).send('OK');
// 4. Procesar el evento
const event = req.body.event;
switch (event.type) {
case 'payment.approved':
console.log('Pago aprobado:', req.body.payment.id);
// Actualizar orden en tu base de datos
break;
case 'cashout.processed':
console.log('Dispersión procesada:', req.body.cashout.id);
break;
default:
console.log('Evento recibido:', event.type);
}
});
app.listen(3000);
import hmac
import hashlib
import json
from flask import Flask, request
app = Flask(__name__)
WEBHOOK_SECRET = 'wh_tok_TU_SECRETO'
WEBHOOK_TOKEN = 'wh_hdr_TU_TOKEN'
@app.route('/webhooks/onepay', methods=['POST'])
def handle_webhook():
# 1. Verificar el token de autenticación
token = request.headers.get('x-webhook-token')
if token != WEBHOOK_TOKEN:
return 'Token inválido', 401
# 2. Verificar la firma HMAC
payload = json.dumps(request.json, separators=(',', ':'))
signature = hmac.new(
WEBHOOK_SECRET.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
# 3. Responder 200 inmediatamente
# 4. Procesar el evento
event = request.json.get('event', {})
event_type = event.get('type')
if event_type == 'payment.approved':
payment = request.json.get('payment', {})
print(f"Pago aprobado: {payment.get('id')}")
# Actualizar orden en tu base de datos
elif event_type == 'cashout.processed':
cashout = request.json.get('cashout', {})
print(f"Dispersión procesada: {cashout.get('id')}")
return 'OK', 200
if __name__ == '__main__':
app.run(port=3000)
Probar con Ngrok (desarrollo local)
Si estás desarrollando en local, usa Ngrok para exponer tu servidor:Copia la URL generada (ej: https://abc123.ngrok.io/webhooks/onepay) y configúrala como URL del webhook en el panel de OnePay.
Eventos disponibles
Pagos
| Evento | Descripción |
|---|
payment.created | Se creó una solicitud de pago |
payment.approved | El cliente completó el pago exitosamente |
payment.rejected | El pago fue rechazado |
payment.expired | El link de pago expiró |
payment.deleted | Se eliminó la solicitud de pago |
Cargos (Débitos)
| Evento | Descripción |
|---|
charge.succeeded | Cargo exitoso |
charge.declined | Cargo rechazado |
Dispersiones
| Evento | Descripción |
|---|
cashout.created | Dispersión creada |
cashout.processing | Dispersión en proceso |
cashout.processed | Dispersión completada exitosamente |
cashout.cancelled | Dispersión cancelada |
cashout.rejected | Dispersión rechazada |
Suscripciones
| Evento | Descripción |
|---|
subscription.created | Suscripción creada |
subscription.payment | Se generó un cobro de suscripción |
subscription.cancelled | Suscripción cancelada |
Cuentas
| Evento | Descripción |
|---|
account.enrolled | Cuenta bancaria vinculada exitosamente |
account.failed | Vinculación de cuenta falló |
Estructura del payload
Cada webhook tiene la siguiente estructura:
{
"payment": {
"id": "9e5ccd4a-d2f0-49dd-87fc-a0da752bd166",
"amount": 150000,
"status": "succeeded",
...
},
"event": {
"type": "payment.approved",
"timestamp": 1689262934,
"environment": "test"
}
}
El nombre del objeto principal varía según el tipo de evento (payment, charge, cashout, subscription, etc.).
Buenas prácticas
Siempre responde 200 OK antes de procesar el evento. Si tu servidor tarda mucho en responder, OnePay podría considerar que el webhook falló e intentar reenviarlo.
- Verifica la firma: Siempre valida el HMAC antes de procesar el evento
- Idempotencia: Usa el
event.type + el ID del recurso para evitar procesar un evento duplicado
- Responde rápido: Retorna
200 inmediatamente y procesa el evento en segundo plano
- Registra los eventos: Guarda un log de todos los webhooks recibidos para debugging
- Diferencia ambientes: El campo
event.environment indica si es test o live
Errores comunes
| Problema | Causa | Solución |
|---|
| No recibo webhooks | URL incorrecta o inaccesible | Verifica que tu URL sea pública y responda a POST |
| Firma inválida | Secreto incorrecto o payload modificado | Verifica que usas el secreto correcto y no modificas el body |
| Eventos duplicados | Tu servidor respondió con error | Implementa idempotencia verificando el ID del evento |
| Webhooks en local | localhost no es accesible | Usa Ngrok u otra herramienta de tunneling |
Siguiente paso