CommunityProgramación y desarrollogithub.com

Nomba Webhooks

Crea un endpoint seguro de webhook de Nomba que verifique la firma, desduplique las entregas y actúe correctamente ante cada evento (payment_success, payment_failed, payment_reversal, payout_success, payout_failed, payout_refund).

¿Qué es Nomba Webhooks?

Este skill permite implementar un endpoint de webhook de Nomba de forma segura y robusta en múltiples frameworks y lenguajes (Express, Next.js, NestJS, Fastify, Flask, FastAPI, Django, Laravel, Symfony o PHP plano). Su propósito principal es verificar que cada solicitud entrante proviene realmente de Nomba usando el SDK oficial (que maneja la firma HMAC correctamente), desduplicar eventos mediante el campo `requestId` para evitar procesar dos veces la misma notificación, y actuar según el tipo de evento (`payment_success`, `payment_failed`, `payment_reversal`, `payout_success`, `payout_failed`, `payout_refund`). El skill también enfatiza la importancia de confirmar decisiones económicas (como marcar un pago como completado o reembolsar) consultando la API de Nomba, ya que la firma del webhook no cubre montos ni referencias. Incluye tablas de referencia, ejemplos de código en Node.js y Python, y una lista de verificación para asegurar una implementación correcta. Está pensado tanto para crear un nuevo manejador como para auditar uno existente.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/NombaOSS/skills/tree/HEAD/skills/nomba-webhooks

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Webhooks de Nomba

Un webhook es la forma en que Nomba le dice a tu servidor que el dinero realmente se movió. Cualquier persona en Internet puede hacer POST a tu URL de webhook, por lo que el manejador debe probar que cada solicitud proviene de Nomba antes de marcar un pedido como pagado o reembolsar a alguien. También debe lidiar con los reintentos de Nomba del mismo evento.

Usa el verificador del SDK oficial; no implementes manualmente el HMAC. La firma de Nomba cubre campos específicos unidos con :, y los SDKs también bloquean un truco de replay que una implementación ingenua pasaría por alto.

LenguajeVerificar
Node.js / TS (@nombaoss/nomba-node)verifyWebhook(rawBody, headers, signingKey)
Python (nomba)from nomba.webhooks import verify_signature → verify_signature(raw_body, headers, signing_key)
PHP (nombaoss/nomba-php)Nomba\Webhook::verify($rawBody, $headers, $signingKey)

Las importaciones exactas y los tipos de argumentos para cada lenguaje están en references/sdk-setup.md. Cada uno devuelve el evento analizado o lanza una excepción (WebhookVerificationError / WebhookVerificationException). Ninguno necesita credenciales de API, solo la clave de firma del webhook desde el panel de Nomba (Desarrollador → Configuración de Webhook). Léela desde una variable de entorno como NOMBA_WEBHOOK_SIGNING_KEY. Es un secreto diferente al secreto del cliente de la API.

El manejador, paso a paso

  1. Obtén el cuerpo sin procesar. Verifica los bytes exactos que Nomba envió. Si un analizador JSON se ejecuta primero y vuelves a serializar el objeto, la firma puede dejar de coincidir, o peor, terminas verificando algo diferente de lo que ejecutas. Cada framework tiene una forma diferente de obtener el cuerpo sin procesar; consulta references/frameworks.md para la correcta, y excluye la ruta de la protección CSRF cuando el framework la tenga.
  2. Verifica, luego analiza. Usa el evento que devuelve el verificador. En caso de fallo, responde con 401 (o 400) y detente. No registres el cuerpo completo ni los encabezados de las solicitudes rechazadas a nivel de información.
  3. Desduplica por requestId. Nomba reintenta las entregas, por lo que el mismo evento puede llegar más de una vez. Registra cada requestId procesado con una restricción única y omite los que ya hayas visto. Devuelve 200 también para los duplicados, o Nomba seguirá reintentando.
  4. Reconoce rápido. Devuelve 200 rápidamente y realiza el trabajo lento (correos electrónicos, cumplimiento, actualizaciones de contabilidad) en un trabajo en segundo plano claveado por requestId. Los manejadores lentos causan tiempos de espera y redeliveries.
  5. Actúa según el evento utilizando la tabla a continuación. Empareja el evento con tu propio registro mediante la referencia, y verifica que el monto y la moneda coincidan con lo que esperabas antes de cumplir.

Qué significa cada evento

event_typeCoincide conHacer
payment_successcheckout: data.order.orderReference; cuenta virtual / transferencia entrante: data.transaction.aliasAccountReference o transactionIdMarcar como pagado después de verificar data.transaction.transactionAmount (y moneda) contra tu pedido. Para checkouts tokenizados, almacena data.tokenizedCardData.tokenKey (si está presente y no es "N/A") con el cliente, solo del lado del servidor.
payment_failedigual que arribaMarcar el intento como fallido; permite que el cliente reintente. No se cobró nada.
payment_reversaligual que arribaEl dinero que recibiste fue revertido. Deshaz el cumplimiento o señálalo para revisión.
payout_successdata.transaction.merchantTxRefMarcar la transferencia o el pago de factura como entregado.
payout_faileddata.transaction.merchantTxRefNo reembolses todavía. Nomba puede reintentar a través de otra ruta. Márcalo como fallido y espera.
payout_refunddata.transaction.merchantTxRefEl dinero ha vuelto a tu billetera de Nomba. Ahora es seguro reembolsar o acreditar a tu cliente y marcar el pago como fallido.

Reembolsar en payout_failed (o en un tiempo de espera) en lugar de payout_refund es la forma clásica en que los comerciantes pagan dos veces a los clientes. Para cualquier evento que no reconozcas, regístralo y devuelve 200.

Confirma las decisiones económicas con la API. La firma de Nomba cubre solo event_type, requestId, data.merchant.userId, data.merchant.walletId, data.transaction.transactionId, type, time, responseCode y la marca de tiempo. El monto, orderReference, merchantTxRef y el tokenKey de la tarjeta no están firmados. Por lo tanto, antes de cumplir, reembolsar o almacenar un token de tarjeta, vuelve a consultar con el SDK y actúa según la respuesta de la API: transactions.get({ orderReference }) para pagos de checkout (también checkout.getTransaction en producción) y transactions.get({ merchantTxRef }) para pagos. Desduplicar en el requestId firmado detiene una copia modificada de un evento que ya has procesado, y la re-consulta cubre el resto.

Protección contra replay

Desduplicar por requestId es la defensa principal: una entrega repetida o reintentada de un evento que ya has procesado no cambia nada.

Los verificadores también pueden rechazar marcas de tiempo antiguas (toleranceSeconds / tolerance_seconds / el cuarto argumento en PHP), pero deja esto desactivado por defecto. Nomba documenta nomba-timestamp como la hora en que se envió el payload, sin decir si los reintentos automáticos obtienen una nueva. Si mantienen la original, una tolerancia más corta que la ventana de reintento de Nomba rechazaría reintentos legítimos después de una interrupción, y los pedidos pagados nunca se confirmarían. Habilítalo solo después de confirmar en el sandbox que las redeliveries llevan una nueva marca de tiempo, y luego mantenlo generoso (por ejemplo, una hora) con los relojes del servidor sincronizados.

Ejemplo (Node.js + Express)

import express from "express";
import { verifyWebhook, WebhookEventType } from "@nombaoss/nomba-node";

const app = express();

// Cuerpo sin procesar solo para esta ruta; regístralo antes de cualquier express.json() global.
app.post("/webhooks/nomba", express.raw({ type: "*/*" }), async (req, res) => {
  let event;
  try {
    event = verifyWebhook(req.body, req.headers, process.env.NOMBA_WEBHOOK_SIGNING_KEY!);
  } catch {
    return res.sendStatus(401);
  }

  // Restricción única en request_id; devuelve false si ya lo hemos visto.
  const isNew = await webhookEvents.recordOnce(event.requestId, event.event_type);
  if (isNew) await jobs.enqueue("nomba-webhook", event); // procesar en segundo plano
  res.sendStatus(200);
});

Python (Flask/FastAPI/Django), PHP (Laravel/plano) y otros frameworks de Node están en references/frameworks.md.

Pruebas

  • Prueba unitaria del manejador con el vector de prueba documentado del SDK: cuerpo de webhook-fixture.json del SDK, clave SampleSignatureKey123, encabezado nomba-timestamp: 2025-09-29T10:51:44Z, nomba-signature: q30yFZDyykoymBf9+mt1wz5UAuLk57raPGPHlGzIv00=. Afirma que una clave incorrecta, un cuerpo manipulado y encabezados faltantes son rechazados, y que un requestId duplicado se procesa una vez.
  • De extremo a extremo: establece una URL de webhook de prueba y una clave de firma en el panel, expón tu servidor local con un túnel y realiza pagos en sandbox. El panel puede reenviar o depurar entregas (https://developer.nomba.com/docs/api-basics/troubleshoot-webhooks).

Lista de verificación

  • El cuerpo sin procesar se verifica con el SDK, antes de cualquier análisis o lógica de negocio.
  • La clave de firma proviene del entorno, no del código.
  • Las firmas incorrectas reciben 401/400; no ocurre nada más.
  • requestId se desduplica con una restricción única; los duplicados aún reciben 200.
  • El trabajo pesado se ejecuta en segundo plano; el manejador devuelve 200 rápidamente.
  • El monto y la moneda se verifican contra el pedido antes de cumplir.
  • Los reembolsos ocurren solo en payout_refund (o una re-consulta que devuelva REFUND).
  • La ruta está excluida de la protección CSRF y usa HTTPS.

Skills relacionados