Saltar al contenido

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

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.

  1. 01

    Abre las notificaciones

    En el Business Portal, ve a Configurations → Clients → Notifications.

  2. 02

    Agrega tu URL HTTPS

    Registra el endpoint HTTPS público de tus servidores que recibirá los eventos.

  3. 03

    Acota la entrada

    Elige un país, o All, y la cuenta a la que pertenecen las notificaciones.

  4. 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
webhook.js
// 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

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.

¿Cómo podemos ayudarte?

Descarga tu ejemplar

Déjanos tus datos y obtén la edición completa al instante.