Enterprise · Plan requerido

Pay Alert para Desarrolladores

Recibí notificaciones de pago en tiempo real en tu propio sistema. Pay Alert envía un POST firmado a tu servidor cada vez que ocurre un evento en tu comercio.

Cómo funciona

Flujo de un webhook

01

Registrás tu endpoint

Creás un webhook en Pay Alert con la URL de tu servidor y los eventos que querés recibir.

02

Ocurre un pago

Cuando un cliente paga, Pay Alert procesa el evento y envía un POST firmado a tu URL en segundos.

03

Verificás y procesás

Tu servidor verifica la firma HMAC-SHA256, parsea el JSON y ejecuta tu lógica de negocio.

Seguridad

Verificación de firma

Cada request incluye el header X-Pay-Alert-Signature con un HMAC-SHA256 del body firmado con tu secret. Siempre verificar antes de procesar.

Importante: leer el body como string crudo antes de parsearlo como JSON. Modificar el body antes de verificar la firma causará fallos de validación.

Node.js

const crypto = require('crypto')

function verifySignature(secret, body, signature) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  )
}

// Express / Fastify — leer el body como string crudo
app.post('/webhook/pay-alert', (req, res) => {
  const sig = req.headers['x-pay-alert-signature']
  const rawBody = req.rawBody // bodyParser con { verify } o similar

  if (!verifySignature(process.env.WEBHOOK_SECRET, rawBody, sig)) {
    return res.status(401).send('Firma inválida')
  }

  const event = JSON.parse(rawBody)
  console.log('Evento recibido:', event.event, event.data)

  res.sendStatus(200)
})

Python

import hmac
import hashlib

def verify_signature(secret: str, body: bytes, signature: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode(),
        body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

# Flask
from flask import Flask, request

app = Flask(__name__)

@app.route('/webhook/pay-alert', methods=['POST'])
def webhook():
    sig = request.headers.get('X-Pay-Alert-Signature', '')
    raw_body = request.get_data()

    if not verify_signature(os.environ['WEBHOOK_SECRET'], raw_body, sig):
        return 'Firma inválida', 401

    event = request.get_json()
    print('Evento:', event['event'], event['data'])

    return '', 200

PHP

<?php
function verifySignature(string $secret, string $body, string $signature): bool {
    $expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
    return hash_equals($expected, $signature);
}

$secret = getenv('WEBHOOK_SECRET');
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PAY_ALERT_SIGNATURE'] ?? '';

if (!verifySignature($secret, $rawBody, $signature)) {
    http_response_code(401);
    exit('Firma inválida');
}

$event = json_decode($rawBody, true);
error_log('Evento: ' . $event['event']);

http_response_code(200);

Referencia

Estructura del payload

Pay Alert envía un JSON con esta estructura en todos los eventos.

{
  "event": "payment.approved",
  "businessId": "cmpa61k6h0002y5sf33s3ixaj",
  "timestamp": "2026-06-18T14:32:00.000Z",
  "data": {
    "id": "cmpx9k2ab0001y5wnabc12345",
    "mpPaymentId": "123456789",
    "amount": "15000.00",
    "currency": "ARS",
    "status": "APPROVED",
    "description": "Producto XYZ",
    "payerName": "Juan García",
    "payerEmail": null,
    "paymentMethod": "account_money",
    "paidAt": "2026-06-18T14:31:58.000Z",
    "receivedAt": "2026-06-18T14:32:00.000Z"
  }
}
eventstring

Tipo de evento. Ver tabla de eventos.

businessIdstring

ID del comercio en Pay Alert.

timestampstring (ISO 8601)

Fecha y hora del envío en UTC.

data.idstring

ID interno del pago en Pay Alert.

data.mpPaymentIdstring

ID del pago en Mercado Pago.

data.amountstring (decimal)

Monto del pago.

data.currencystring

Código ISO 4217. Ej: "ARS".

data.statusstring

Estado: APPROVED | REJECTED | REFUNDED | CANCELLED.

data.payerNamestring | null

Nombre del pagador (si está disponible).

data.payerEmailstring | null

Email del pagador (si está disponible).

data.paymentMethodstring | null

Método: account_money, bank_transfer, credit_card, etc.

data.paidAtstring | null

Fecha de aprobación del pago.

data.receivedAtstring

Fecha en que Pay Alert recibió el evento de MP.

Eventos

Eventos disponibles

Suscribite solo a los eventos que tu sistema necesita. Un array vacío recibe todos.

EventoCuándo se dispara
payment.approvedUn pago es aprobado por Mercado Pago. Incluye pagos nuevos y actualizaciones de estado.
payment.receivedPay Alert recibe un pago aprobado por primera vez (sin procesar antes).
payment.refundedUn pago es reembolsado total o parcialmente.
payment.cancelledUn pago pendiente es cancelado.

API REST

Gestionar webhooks

Los webhooks se administran vía API con tu access token JWT. Rol mínimo requerido: ADMIN. El plan Enterprise es necesario para crear webhooks.

POST/api/v1/businesses/{businessId}/webhooks

Crea un webhook. Devuelve el secret solo en la creación — guardalo de forma segura.

curl -X POST https://pay-alert-api.onrender.com/api/v1/businesses/{businessId}/webhooks \
  -H "Authorization: Bearer <tu-access-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://tu-servidor.com/webhook/pay-alert",
    "events": ["payment.approved", "payment.refunded"],
    "isActive": true
  }'
GET/api/v1/businesses/{businessId}/webhooks

Lista todos los webhooks del comercio (sin el secret).

curl https://pay-alert-api.onrender.com/api/v1/businesses/{businessId}/webhooks \
  -H "Authorization: Bearer <tu-access-token>"
POST/api/v1/businesses/{businessId}/webhooks/{webhookId}/test

Envía un evento de prueba payment.approved a la URL configurada. Útil para verificar que tu endpoint responde correctamente.

curl -X POST https://pay-alert-api.onrender.com/api/v1/businesses/{businessId}/webhooks/{webhookId}/test \
  -H "Authorization: Bearer <tu-access-token>" \
  -H "Content-Type: application/json" \
  -d '{}'
PUT/api/v1/businesses/{businessId}/webhooks/{webhookId}

Actualiza url, events, isActive. Con regenerateSecret: true rota el secret y devuelve el nuevo valor.

curl -X PUT https://pay-alert-api.onrender.com/api/v1/businesses/{businessId}/webhooks/{webhookId} \
  -H "Authorization: Bearer <tu-access-token>" \
  -H "Content-Type: application/json" \
  -d '{ "regenerateSecret": true }'

Autenticación server-to-server

Enterprise

API Keys

Para llamar a la API desde tu backend sin un usuario logueado (por ejemplo, para registrar pagos esperados), usá una API key en lugar de un JWT. Solo el rol OWNER puede crearlas o revocarlas, y siempre vía tu access token — las keys no pueden gestionarse con otra key.

El valor crudo de la key se muestra una sola vez, en la respuesta de creación. Guardalo de forma segura — si lo perdés, tenés que revocar la key y crear una nueva. Máximo 10 keys activas por comercio.
POST/api/v1/businesses/{businessId}/api-keys

Crea una API key. Devuelve key (formato pa_live_...) solo en este response.

curl -X POST https://pay-alert-api.onrender.com/api/v1/businesses/{businessId}/api-keys \
  -H "Authorization: Bearer <tu-access-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Integración ERP",
    "expiresAt": "2027-01-01T00:00:00.000Z"
  }'
GET/api/v1/businesses/{businessId}/api-keys

Lista las keys activas del comercio (sin el valor crudo).

curl https://pay-alert-api.onrender.com/api/v1/businesses/{businessId}/api-keys \
  -H "Authorization: Bearer <tu-access-token>"
DELETE/api/v1/businesses/{businessId}/api-keys/{keyId}

Revoca una key de forma inmediata para nuevos requests. Por caché, una key ya revocada puede seguir aceptándose hasta 5 minutos.

curl -X DELETE https://pay-alert-api.onrender.com/api/v1/businesses/{businessId}/api-keys/{keyId} \
  -H "Authorization: Bearer <tu-access-token>"

Usar la key en tus requests

Mandala como Authorization: Bearer pa_live_... o como header x-api-key: pa_live_.... Los endpoints que aceptan API key también aceptan JWT — usá lo que tenga sentido según si hay o no un usuario logueado del lado que llama. Límite: 300 requests/minuto por key.

Transferencias y pagos fuera de Mercado Pago

Enterprise

Pagos esperados

Registrá de antemano un pago que esperás recibir (típicamente una transferencia) con una referencia propia y un monto. Cuando Pay Alert detecta un pago que matchea, lo vincula automáticamente — vos solo consultás el estado. Auth: JWT o API Key.

POST/api/v1/businesses/{businessId}/expected-payments

Crea un pago esperado. externalReference es tu propio identificador (ej: número de factura) y es de un solo uso por comercio — no se puede reutilizar aunque el pago original haya expirado o se haya cancelado. expiresAt es opcional.

curl -X POST https://pay-alert-api.onrender.com/api/v1/businesses/{businessId}/expected-payments \
  -H "Authorization: Bearer pa_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "externalReference": "factura-4521",
    "amount": 15000,
    "expiresAt": "2026-07-25T00:00:00.000Z"
  }'
GET/api/v1/businesses/{businessId}/expected-payments/{externalReference}

Consulta el estado. Si pasó expiresAt sin match, el estado se resuelve a EXPIRED automáticamente. Cuando el estado es MATCHED, incluye el pago vinculado en data.payment.

curl https://pay-alert-api.onrender.com/api/v1/businesses/{businessId}/expected-payments/factura-4521 \
  -H "Authorization: Bearer pa_live_..."
{
  "data": {
    "id": "cmpz1a2b30001y5abcd12345",
    "externalReference": "factura-4521",
    "amount": "15000.00",
    "status": "MATCHED",
    "expiresAt": "2026-07-25T00:00:00.000Z",
    "createdAt": "2026-07-20T18:04:11.000Z",
    "payment": {
      "id": "cmpx9k2ab0001y5wnabc12345",
      "mpPaymentId": "123456789",
      "amount": "15000.00",
      "currency": "ARS",
      "status": "APPROVED",
      "description": "Producto XYZ",
      "payerEmail": null,
      "payerName": "Juan García",
      "paymentMethod": "account_money",
      "paidAt": "2026-07-20T18:05:02.000Z",
      "receivedAt": "2026-07-20T18:05:03.000Z"
    }
  }
}
DELETE/api/v1/businesses/{businessId}/expected-payments/{externalReference}

Cancela un pago esperado. Solo funciona mientras sigue PENDING — si ya fue matcheado, expiró, o ya está cancelado, devuelve error.

curl -X DELETE https://pay-alert-api.onrender.com/api/v1/businesses/{businessId}/expected-payments/factura-4521 \
  -H "Authorization: Bearer pa_live_..."

Estados posibles

PENDINGMATCHED, EXPIRED o CANCELLED. Son estados terminales — un pago esperado nunca vuelve a PENDING.

Cómo matchea

Se vincula automáticamente solo si hay exactamente un pago esperado PENDING vigente con el mismo monto (tolerancia ±0.01). Si hay ambigüedad, no matchea nada — consultá por polling o usá un webhook para enterarte.

Buenas prácticas

Implementación recomendada

Responder 200 rápido

Devolvé 200 antes de procesar la lógica pesada. Si tardás más de 10 segundos, Pay Alert considera que falló la entrega.

Idempotencia

El mismo evento puede llegar más de una vez. Usá data.id o data.mpPaymentId como clave de idempotencia en tu base de datos.

Siempre verificar firma

Nunca procesar un webhook sin verificar X-Pay-Alert-Signature. Cualquiera puede hacer un POST a tu URL.

Rotación de secret

Si sospechás que tu secret fue comprometido, regeneralo desde la API con regenerateSecret: true. Actualizá tu variable de entorno de inmediato.

Para plataformas

¿Tu software gestiona el negocio de otros comercios?

Si desarrollás un ERP, POS o sistema de gestión y necesitás que tus clientes tengan Pay Alert activo sin pasar por nuestra UI, nuestra arquitectura ya está preparada para ese tipo de integración a nivel de plataforma. Escribinos y lo evaluamos juntos.

¿Necesitás ayuda con la integración?

Para clientes Enterprise, el soporte técnico está incluido. También podemos desarrollar la integración a medida para tu sistema.