¿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.createdSe creó una solicitud de pago payment.approvedEl cliente completó el pago exitosamente payment.rejectedEl pago fue rechazado payment.expiredEl link de pago expiró payment.deletedSe eliminó la solicitud de pago
Cargos (Débitos)
Evento Descripción charge.succeededCargo exitoso charge.declinedCargo rechazado
Dispersiones
Evento Descripción cashout.createdDispersión creada cashout.processingDispersión en proceso cashout.processedDispersión completada exitosamente cashout.cancelledDispersión cancelada cashout.rejectedDispersión rechazada
Suscripciones
Evento Descripción subscription.createdSuscripción creada subscription.paymentSe generó un cobro de suscripción subscription.cancelledSuscripción cancelada
Cuentas
Evento Descripción account.enrolledCuenta bancaria vinculada exitosamente account.failedVinculació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 accesibleUsa Ngrok u otra herramienta de tunneling
Siguiente paso
Cobrar con link de pago Crea tu primer cobro y recibe el webhook de pago aprobado.
Dispersar dinero Envía dinero y recibe el webhook de dispersión procesada.