Webhooks
Entérate en cuanto se mueve el dinero
Notificaciones en tiempo real de pay-ins, payouts, cuentas virtuales, suscripciones, cambio de divisas, Wire In y Wire Out, cada una firmada con HMAC-SHA256 para que verifiques cada solicitud antes de actuar.
- Firmas HMAC-SHA256
- Reintentos con backoff
- Por país y por cuenta
- 09:41:02.11409:41:02PayIn100 INPROGRESS20084 ms
- 09:41:05.90209:41:05PayIn200 COMPLETED20091 ms
- 09:42:18.33009:42:18PayOut100 INPROGRESS20077 ms
- 09:42:40.01809:42:40PayOut200 COMPLETED20088 ms
- 09:43:00.55109:43:00VirtualAccount200 COMPLETED20080 ms
POST https://example-merchant.com/webhooks/localpayment
x-Signature: HMAC-SHA256 3f9a1c0e…b47e2b
{ "transactionType": "PayIn", "transactionFlow": "DIRECT",
"data": { "externalId": "order_8F2K-1043",
"status": { "code": "200", "description": "COMPLETED" } } }Configuración
Configura las notificaciones en el Business Portal
Registrar un endpoint no requiere código. Cada entrada puede acotarse por país y por cuenta.
- 01
Abre las notificaciones
En el Business Portal, ve a Configurations → Clients → Notifications.
- 02
Agrega tu URL HTTPS
Registra el endpoint HTTPS público de tus servidores que recibirá los eventos.
- 03
Acota la entrada
Elige un país, o All, y la cuenta a la que pertenecen las notificaciones.
- 04
Agrega encabezados y prueba
Si quieres, suma encabezados personalizados para tu gateway y luego haz una transacción de prueba en Stage.
Payload
Un mismo sobre para cada evento
Todas las notificaciones comparten los mismos campos raíz, así un solo receptor puede enrutarlas. El cuerpo es JSON y llega con Content-Type text/plain.
- transactionType: PayIn, PayOut, VirtualAccount, Subscription, CurrencyExchange, WireIn o WireOut
- transactionFlow: el flujo de procesamiento, útil para enrutar
- data: la transacción en sí, con externalId, internalId y status
Eventos
De qué te notificamos
Cobro con tarjeta
- transactionType
- PayIn
- Qué te indica
- Aprobado, completado, rechazado, reembolsado o con contracargo
Cobro por transferencia o medio local
- transactionType
- PayIn
- Qué te indica
- El estado final después de que el pagador confirma en su banco o billetera
Cuenta virtual
- transactionType
- VirtualAccount
- Qué te indica
- Cuenta creada y cada pago recibido
Suscripción
- transactionType
- Subscription
- Qué te indica
- Ciclo de vida de la suscripción; cada cargo llega además como evento PayIn
Payout
- transactionType
- PayOut
- Qué te indica
- Cambios de estado a lo largo del flujo de payout, hasta completarse
Cambio de divisas
- transactionType
- CurrencyExchange
- Qué te indica
- Resultado de una conversión entre tus saldos
Wire In
- transactionType
- WireIn
- Qué te indica
- Fondos acreditados en tu cuenta, en moneda fiat o USDC/USDT
Wire Out
- transactionType
- WireOut
- Qué te indica
- Retiro enviado; los Wire Out en cripto incluyen el hash de la transacción en blockchain
Los esquemas completos y los códigos de estado de cada evento están en la referencia de webhooks en docs.localpayment.com.
Seguridad
Verifica cada firma
Cada solicitud trae x-Signature: HMAC-SHA256 seguido del HMAC en hexadecimal del cuerpo crudo, calculado con tu secreto de webhook. Recalcúlalo y compáralo antes de confiar en el payload.
- Lee el cuerpo crudo: es JSON enviado como Content-Type text/plain
- Quita el prefijo “HMAC-SHA256 ” y compara con una función de tiempo constante
- Rechaza las firmas que no coinciden con 401 y nunca registres el payload completo
// Receptor Express para webhooks de Localpayment (Node.js 18+).
import crypto from 'node:crypto';
import express from 'express';
import { jobQueue } from './queue.js'; // tu propia cola de trabajos asíncronos
const app = express();
// Carga el secreto del webhook desde tu gestor de secretos al iniciar. Nunca lo escribas en el código.
const WEBHOOK_SECRET = process.env.LP_WEBHOOK_SECRET;
if (!WEBHOOK_SECRET) throw new Error('LP_WEBHOOK_SECRET is not configured');
function hasValidSignature(rawBody, header) {
if (typeof rawBody !== 'string') return false;
// Formato del header: "x-Signature: HMAC-SHA256 <hex>". Quita el prefijo primero.
const received = String(header ?? '').replace(/^HMAC-SHA256\s+/i, '').trim();
if (!/^[0-9a-f]{64}$/i.test(received)) return false;
const expected = crypto.createHmac('sha256', WEBHOOK_SECRET).update(rawBody, 'utf8').digest();
return crypto.timingSafeEqual(Buffer.from(received, 'hex'), expected);
}
// El cuerpo es JSON enviado como Content-Type text/plain: conserva el texto crudo para el HMAC.
app.post('/webhooks/localpayment', express.text({ type: '*/*', limit: '1mb' }), (req, res) => {
if (!hasValidSignature(req.body, req.get('x-signature'))) return res.sendStatus(401);
let event;
try {
event = JSON.parse(req.body);
} catch {
return res.sendStatus(400);
}
res.sendStatus(200); // responde rápido, antes de cualquier trabajo pesado
// Procesa de forma asíncrona. Deduplica por internalId/externalId + estado
// para aplicar una sola vez cada reintento. Nunca registres el payload completo.
jobQueue.enqueue(event);
});Procesamiento confiable
Pensado para reintentos y redes reales
Responde rápido
Devuelve 200 en cuanto la firma es válida y haz el trabajo pesado en un proceso en segundo plano.
Espera reintentos
Si no recibimos un 200, reintentamos con backoff exponencial. Tu receptor debe poder ejecutarse más de una vez sin efectos duplicados.
Deduplica
Procesa por internalId o externalId más el estado, para aplicar una sola vez cada entrega reintentada.
No asumas un orden
Compara el estado entrante con el que tienes guardado antes de aplicarlo y nunca hagas retroceder una transacción.
Confirma con la API
Ante la duda, consulta GET /api/transactions/{externalId}/status para conocer el estado actual de una transacción.
Registra con cuidado
Guarda tipos de evento, IDs y estados, nunca payloads completos, tokens ni datos personales.
Operaciones
Revisa cada entrega en el Business Portal
Los reportes de notificaciones muestran qué se envió a tus endpoints, para que soporte e ingeniería resuelvan incidencias sin revisar los registros del servidor.
- Registro de las notificaciones enviadas a tus endpoints
- Búsqueda de transacciones y reportes de actividad detallados
- Webhooks, lista de IP permitidas y usuarios en un solo lugar
Resumen
USD
1.284.560,00
+4,2 %
BRL
3.420.118,40
+12,8 %
MXN
8.905.332,10
+6,1 %
- Pay-in · Pixorder_8F2K-1043+ R$ 1.250,00
- Payout · SPEIseller_0921− MX$ 18.400,00
- FX · BRL → USDfx_3b7q250.000,00 USD
- Pay-in · Bre-Border_9A1C-2210+ COP 890.000
Preguntas frecuentes sobre webhooks
¿Cómo sé que un webhook viene realmente de Localpayment?
Verifica el encabezado x-Signature: recalcula el HMAC-SHA256 del cuerpo crudo con tu secreto de webhook y compáralos con una función de tiempo constante. También puedes agregar encabezados personalizados a cada entrada para tu gateway.
¿Cada cuánto se reintentan las entregas fallidas?
Las entregas que no reciben un 200 se reintentan con backoff exponencial. Diseña tu receptor para varios intentos y revisa los reportes de notificaciones del Business Portal para ver el historial de entregas.
¿Por qué el Content-Type es text/plain?
El cuerpo es JSON enviado como text/plain. Léelo como texto crudo, verifica la firma sobre esos bytes exactos y solo entonces interpreta el JSON.
¿Puedo enviar distintos países a distintos endpoints?
Sí. Cada entrada de notificación se acota por país (o All) y por cuenta, así puedes dirigir mercados o unidades de negocio a URLs separadas.
¿Qué debe responder mi endpoint?
Un estado 200, lo antes posible y antes de cualquier trabajo largo. Sin un 200, la entrega se considera fallida y se reintenta.
Conecta tu primer webhook
Configura un endpoint en Stage, dispara un cobro de prueba y verifica la firma de punta a punta.

Firmas HMAC-SHA256