Pular para o conteúdo principal

Quando dispara

O evento cash_in é disparado quando:
  • Uma transferência SPEI chega na CLABE descartável emitida por POST /api/spei/cash-in e é liquidada
  • Um pagamento OXXO emitido por POST /api/oxxo/cash-in é pago no caixa da loja e confirmado

Payload

{
  "event": "cash_in",
  "deliveryId": "8e2c5b6f-3a12-4b9c-9a18-77a2b3c4d5e6",
  "createdAt": "2026-05-12T14:31:05.000Z",
  "transaction": {
    "id": 12345,
    "externalId": "order-abc-123",
    "paymentMethod": "SPEI",
    "direction": "in",
    "type": "cash_in",
    "status": "CONFIRMED",
    "provider": "smartfastpay",
    "amountCentavos": 50000,
    "clabe": "012180001234567890",
    "createdAt": "2026-05-12T14:30:00.000Z",
    "confirmedAt": "2026-05-12T14:31:05.000Z"
  }
}

Headers

HeaderValor
X-NTXPay-Eventcash_in
X-NTXPay-Signaturesha256=<hmac>
X-NTXPay-TimestampUnix epoch (segundos)
X-NTXPay-DeliveryUUID único do envio

Diferenças SPEI vs OXXO

AspectoSPEIOXXO
paymentMethodSPEIOXXO
clabeCLABE de origemnull
Tempo até confirmaçãoSegundos a minutosMinutos a horas após pagar no caixa
Identificador adicionalreferenceNumericalreferenceNumerical

Resposta Esperada

Responda 200 OK em menos de 10 segundos. Em caso de qualquer status diferente, o NTX Pay tenta novamente até 5 vezes em backoff exponencial.
HTTP/1.1 200 OK
Content-Type: application/json

{"received": true}

Exemplos de Handler

import express from 'express';
import crypto from 'crypto';

const app = express();
app.use(express.raw({ type: 'application/json' })); // raw body for HMAC

const SECRET = process.env.NTXPAY_WEBHOOK_SECRET!;

app.post('/webhooks/ntxpay', (req, res) => {
  const sig = req.header('X-NTXPay-Signature') ?? '';
  const expected = 'sha256=' + crypto
    .createHmac('sha256', SECRET)
    .update(req.body) // req.body is Buffer
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return res.status(401).end();
  }

  const event = JSON.parse(req.body.toString());
  if (event.event === 'cash_in' && event.transaction.status === 'CONFIRMED') {
    enqueue(event); // process async
  }

  res.json({ received: true });
});