Un pago con Pix — el sistema de pagos instantáneos de Brasil — se confirma en segundos, y tu sistema necesita enterarse al instante, sin consultar la API de Mercado Pago en un loop. Para eso existen los webhooks de notificación. En esta guía vas a configurar las notificaciones desde cero, validar la firma x-signature, armar el flujo completo de un pago Pix y probar todo antes de salir a producción — incluidos los errores que más traban las integraciones reales.
Por qué Mercado Pago notifica vía webhook
Cuando un cliente paga con Pix, la aprobación ocurre en segundos; con tarjeta, el estado puede pasar de in_process a approved o rejected minutos después; con boleto — un comprobante bancario brasileño que se paga en efectivo o desde la app del banco —, días después. Consultar la API cada pocos segundos por cada orden abierta no escala y además choca con los rate limits. El modelo invertido lo resuelve: Mercado Pago envía un POST a tu URL cada vez que ocurre algo relevante, y tu aplicación reacciona al momento.
Históricamente existieron dos mecanismos: el IPN (Instant Payment Notification), hoy discontinuado, y los Webhooks, que son el camino recomendado y el único que deberías usar en integraciones nuevas. Toda la documentación oficial está en la página de notificaciones Webhooks de Mercado Pago. Si todavía no dominás el funcionamiento general de los webhooks (reintentos, idempotencia, respuesta rápida), conviene empezar por nuestra guía Qué es un webhook y cómo funciona.
Configurando las notificaciones
Desde el panel de desarrollador
- Entra a Tus integraciones, selecciona tu aplicación y abre Webhooks → Configurar notificación.
- Registra la URL de tu endpoint — obligatoriamente HTTPS válido y accesible desde internet. Hay dos campos separados: modo prueba (usado con credenciales de prueba) y modo producción.
- Selecciona los eventos que quieres recibir. Para pagos (Pix, tarjeta, boleto), el tópico es
payment, que dispara las accionespayment.createdypayment.updated. - Guarda la firma secreta que se muestra en esa pantalla — con ella vas a validar el header
x-signature. Puede regenerarse en cualquier momento (y en ese caso hay que actualizar tu entorno).
Vía API, por pago
Alternativamente (o en conjunto), puedes informar el campo notification_url al crear el pago o la preferencia de checkout. Es útil cuando cada tienda o tenant de tu sistema tiene su propio endpoint. La notificación llega en el mismo formato; la única diferencia es dónde se definió la URL.
Qué llega a tu endpoint
La notificación es un POST con el identificador del recurso en la query string (data.id y type) y un cuerpo JSON resumido:
{
"id": 987654321,
"live_mode": true,
"type": "payment",
"date_created": "2026-07-25T14:32:07.000-04:00",
"user_id": 44444,
"api_version": "v1",
"action": "payment.updated",
"data": { "id": "12345678901" }
}El flujo completo de un Pix, paso a paso
- Creas el pago Pix y muestras el código QR al cliente.
- El cliente paga en la app de su banco; Mercado Pago aprueba en segundos.
- Mercado Pago envía la notificación (
action: payment.updated) a tu URL, con la firma en el headerx-signature. - Tu endpoint valida la firma y responde
200de inmediato — Mercado Pago espera la respuesta unos 22 segundos; sin ella, considera la entrega fallida y la reenvía después. - Fuera del ciclo de la respuesta, tu aplicación consulta
GET /v1/payments/12345678901con el access token de la cuenta. - Si
status = "approved", la orden se libera; cualquier otro estado solo actualiza el registro interno.
En código (Node + Express), el esqueleto del handler queda así:
app.post("/webhooks/mercadopago", async (req, res) => {
// 1. Rechaza solicitudes sin firma válida (ver sección de abajo)
if (!firmaValida(req)) return res.sendStatus(401);
// 2. Confirma la recepción ya — sin esperar el procesamiento
res.sendStatus(200);
// 3. El trabajo pesado va a una cola, nunca en el ciclo de la respuesta
const { type, data } = req.body;
if (type === "payment") {
await cola.add("procesar-pago", { paymentId: data.id });
}
});const resp = await fetch(
`https://api.mercadopago.com/v1/payments/${paymentId}`,
{ headers: { Authorization: `Bearer ${process.env.MP_ACCESS_TOKEN}` } },
);
const pago = await resp.json();
if (pago.status === "approved" && pago.live_mode) {
await liberarOrden(pago.external_reference);
}Validando la firma x-signature
Tu endpoint es público — cualquiera que descubra la URL puede enviar un POST con un JSON idéntico al de Mercado Pago. La firma es lo que separa una notificación legítima de una falsificada. El header llega en este formato:
x-signature: ts=1704908010,v1=618c85345248dd820d5fd456117c2ab2ef8eda45...
x-request-id: bb56a2f1-6aae-46ac-982e-9dcd3581d08eLa validación tiene cuatro pasos:
- Extrae
tsyv1del headerx-signature. - Arma el manifest con el template oficial:
id:[data.id];request-id:[x-request-id];ts:[ts];— usando eldata.idde la query string y el headerx-request-id. - Calcula el HMAC-SHA256 del manifest en hexadecimal, usando la firma secreta del panel como clave.
- Compara el resultado con
v1usando comparación de tiempo constante.
import crypto from "node:crypto";
function firmaValida(req) {
const firma = req.headers["x-signature"]; // "ts=...,v1=..."
const requestId = req.headers["x-request-id"];
const dataId = req.query["data.id"];
if (!firma || !requestId || !dataId) return false;
const partes = Object.fromEntries(
firma.split(",").map((p) => p.trim().split("=")),
);
if (!partes.ts || !partes.v1) return false;
const manifest = `id:${dataId};request-id:${requestId};ts:${partes.ts};`;
const esperado = crypto
.createHmac("sha256", process.env.MP_WEBHOOK_SECRET)
.update(manifest)
.digest("hex");
return (
esperado.length === partes.v1.length &&
crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(partes.v1))
);
}Eventos que vale la pena suscribir
| Evento (action) | Cuándo se dispara | Qué hacer |
|---|---|---|
payment.created | Pago creado: Pix pendiente, boleto emitido, tarjeta en análisis | Registrar el pago y esperar la actualización |
payment.updated | El estado cambió: aprobado, rechazado, devuelto, cancelado | Consultar GET /v1/payments/{id} y actualizar la orden |
chargebacks | Contracargo abierto o modificado | Suspender la entrega y reunir evidencias de la venta |
subscription_preapproval | Suscripción creada, pausada o cancelada | Sincronizar el estado de la suscripción en tu sistema |
Cómo probar antes de salir a producción
- Cuentas de prueba. Crea un par vendedor/comprador en "Cuentas de prueba" en el panel. El vendedor de prueba tiene credenciales propias — con ellas creas pagos de sandbox y recibes notificaciones en el endpoint de modo prueba.
- Simulador de notificaciones. En la pantalla de configuración de Webhooks hay un botón de simulación que envía una notificación de ejemplo a tu URL — la forma más rápida de verificar conectividad y validación de firma.
- Inspecciona primero el payload real. Antes de escribir el handler, apunta la URL de prueba a un endpoint de inspección (una URL temporal que captura y muestra cada solicitud, con headers y body). Ver un
x-signaturey un cuerpo reales elimina la mitad de las dudas de integración. - Desarrollo local. Mercado Pago no alcanza
localhost— vas a necesitar un túnel o reenvío de eventos. Las opciones están comparadas en Cómo probar webhooks en localhost. - Tarjetas y pagadores de prueba. Usa las tarjetas de prueba documentadas (aprobación, rechazo por saldo, rechazo por seguridad) para simular cada camino de tu flujo, y el Pix de sandbox para el flujo instantáneo.
Troubleshooting: los problemas clásicos
La notificación nunca llega
Casi siempre es infraestructura: URL con HTTPS inválido (certificado vencido o self-signed), firewall/WAF bloqueando las IPs de Mercado Pago, o la URL registrada en el modo equivocado (prueba × producción). Confirma con el simulador del panel y verifica que tu endpoint responda en menos de 22 segundos — una respuesta lenta cuenta como falla, aunque el código sea 200.
401 al consultar el pago
El data.id recibido pertenece a la cuenta cuyas credenciales crearon el pago. Un error común es recibir la notificación de la cuenta de prueba y consultar la API con el access token de producción (o de otra aplicación). Notificación y consulta deben usar el mismo par de credenciales.
Notificaciones duplicadas
Mercado Pago reenvía las notificaciones no confirmadas en ciclos de hasta 15 minutos — y a veces el mismo evento llega dos veces incluso con todo en orden. Trata el procesamiento como idempotente: usa el data.id + el estado consultado como clave e ignora repeticiones. Es el mismo principio general de cualquier webhook, detallado en Los 10 errores más comunes al implementar webhooks.
Notificación de prueba procesada como venta real
Toda notificación trae live_mode en el cuerpo, y el pago consultado también tiene el campo. Revísalo antes de liberar cualquier cosa — órdenes "pagadas" con credenciales de prueba en producción son un clásico vergonzoso.
Boleto "atrasado"
Pix notifica en segundos; el boleto solo cambia de estado con la compensación bancaria (hasta 3 días hábiles). No es un bug — es el medio de pago. Modela tus estados internos para convivir con pagos pendientes durante días.
Resumen
- Usa Webhooks (no IPN): tópico
payment, URL HTTPS por modo (prueba/producción), firma secreta guardada. - Valida el
x-signaturecon el manifest oficialid:[data.id];request-id:[x-request-id];ts:[ts];y HMAC-SHA256. - Responde 200 en menos de 22 segundos y procesa en cola; el estado real viene de
GET /v1/payments/{id}, nunca del cuerpo del webhook. - Idempotencia por
data.id, chequeo delive_modey credenciales consistentes entre notificación y consulta. - Prueba con cuentas de sandbox, el simulador del panel y un endpoint de inspección antes de programar.