Blog · 12 de octubre de 2026

El webhook de Mercado Pago o Stripe que no llegó

El cliente pagó, vio «pago aprobado» y su pedido sigue «pendiente» en tu sistema. Casi siempre es un webhook que no llegó o que llegó y falló. Aquí, cómo saber cuál de los dos fue, las causas más comunes, el código para contestarlo bien y cómo enterarte antes de que el cliente te escriba.

1. Cómo se ve

El cobro pasa en Mercado Pago o en Stripe. Tu sistema se entera por un webhook: una petición que ellos le hacen a tu servidor cuando el pago cambia. Si esa petición no llega o tu servidor contesta con error, pasa esto:

Te enteras cuando el cliente escribe «ya pagué, ¿dónde está mi pedido?». Lo que sigue es para enterarte antes.

2. ¿Se envió? Revísalo en el panel

Lo primero es saber si el proveedor lo mandó y qué le contestó tu servidor. Los dos guardan el historial.

Mercado Pago

Stripe

3. Las causas más comunes

Lo que diga el panel te lleva casi directo a la causa.

Lo que vesQué suele serCómo se arregla
No aparece ningún envíoLa URL no está configurada, el evento no está seleccionado o estás mezclando prueba con producción. En Mercado Pago, una notification_url en la preferencia manda a otra dirección.Revisa la URL y los eventos del modo correcto (prueba y producción tienen URLs y llaves distintas).
400 o 401Tu servidor rechazó la firma. En Stripe, casi siempre porque el cuerpo ya se convirtió a JSON antes de verificarlo: «No signatures found matching the expected signature for payload».Verifica con el cuerpo sin tocar y con el secreto de ese endpoint y ese modo. En Mercado Pago, la clave secreta de esa misma aplicación.
Tiempo agotadoHaces todo (guardar, mandar correos, facturar) antes de contestar.Contesta primero y procesa después (abajo, el código).
500Tu código truena: el pedido no se encontró, la consulta a la API usó el token de otro modo, una columna cambió.Busca en tus logs con el id del evento. Mientras lo arreglas, el proveedor sigue reintentando.
301, 302 o 404La URL cambió: de http a https, con o sin www, o una ruta que ya no existe. Stripe cuenta cualquier redirección como falla.Pon la URL final exacta, la que contesta sin redirigir.
200, pero el pedido no cambióContestaste 200 y luego falló el proceso; llegó otro evento del que esperabas; o llegaron dos veces o en otro orden.Anota cada evento que llega con su id, y no supongas orden: consulta el estado real del pago.
Pago con OXXO en Stripecheckout.session.completed llega cuando el cliente recibe su ficha, con payment_status: "unpaid". El pago llega horas o días después.Surte con checkout.session.async_payment_succeeded, no con completed.
Un detalle de Mercado Pago que confunde: el webhook no trae el estado del pago. Trae el tipo, la acción y el id (data.id). Para saber si se aprobó hay que consultarlo en /v1/payments/{id}, con el access token de la misma cuenta y del mismo modo.

4. Contestarlo bien

La regla es la misma en los dos: verifica la firma, contesta rápido y procesa después. Y anota que llegó, para poder buscarlo.

Stripe, en Express

// el cuerpo sin tocar SOLO en esta ruta; express.json() va después
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {
  let evento;
  try {
    evento = stripe.webhooks.constructEvent(
      req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET);
  } catch (err) {
    console.error('webhook stripe: firma inválida', err.message);
    return res.sendStatus(400);
  }
  res.sendStatus(200);   // primero contestar
  procesar(evento).catch((err) => console.error('webhook stripe', evento.id, err));
});

Mercado Pago, en Express

app.post('/webhooks/mercadopago', express.json(), (req, res) => {
  // valida x-signature con la clave secreta de tu aplicación (los SDK oficiales lo hacen)
  if (!firmaValida(req)) return res.sendStatus(401);
  res.sendStatus(200);   // antes de 22 segundos
  if (req.body.type !== 'payment') return;
  revisarPago(req.body.data.id).catch((err) => console.error('webhook mp', req.body.data.id, err));
});

async function revisarPago(id) {
  const r = await fetch(`https://api.mercadopago.com/v1/payments/${id}`, {
    headers: { Authorization: `Bearer ${process.env.MP_ACCESS_TOKEN}` },
  });
  const pago = await r.json();
  // pago.status: approved, pending, rejected…  pago.external_reference: tu pedido
  if (pago.status === 'approved') await marcarPagado(pago.external_reference);
}

Contestar antes de procesar tiene un costo: si procesar falla, el proveedor ya no reintenta, porque le dijiste que sí. Por eso el siguiente paso.

5. No dependas solo del webhook

El webhook es el camino rápido, no el único. Dos respaldos baratos:

6. Que te avise solo

Todo lo anterior arregla el problema cuando ya sabes que existe. Falta enterarte el mismo día, no cuando escribe el cliente. La idea: anotar dos momentos y que alguien vigile que el segundo llegue.

Con Estiaje son dos líneas en tu código:

// en tu página de «gracias», cuando el pago viene aprobado
estiaje.event('pago.aprobado_por_cliente', { pedido_id, monto });

// en tu webhook, cuando ya marcaste el pedido
estiaje.outcome('pago.confirmado', { pedido_id });

Y en el panel declaras qué esperas, sin tocar más código:

{
  "name":       "Pago aprobado → webhook procesado",
  "when":       "pago.aprobado_por_cliente",
  "expects":    "pago.confirmado",
  "match_by":   "attrs.pedido_id",
  "window_min": 30,
  "valor":      { "tipo": "atributo", "atributo": "monto" }
}

Si un pedido se queda sin cierre, te llega al teléfono algo como «$1,280 en riesgo: Pago aprobado → webhook procesado», con el pedido que se quedó a medias y, si le pones la liga a tu sistema, el botón para abrirlo. Si tu webhook truena, el error llega agrupado, con su stack y el pedido que dejó a medias.

Prueba 14 días de Pro, sin tarjeta Al terminar, sigues en Gratis con 3 procesos vigilados.

Preguntas frecuentes

¿Cada cuánto reintenta Mercado Pago un webhook?

Mercado Pago espera un 200 o 201 durante 22 segundos. Si no lo recibe, reintenta cada 15 minutos; después del tercer intento espacia los reintentos, pero sigue intentando.

¿Cada cuánto reintenta Stripe un webhook?

En modo en vivo, hasta por tres días, cada vez con más espacio entre intentos. Puedes reenviar un evento a mano desde Workbench hasta 15 días después de creado, o con la CLI de Stripe (stripe events resend) hasta 30 días.

¿Qué significa «No signatures found matching the expected signature for payload»?

Que la firma no coincide con el cuerpo que recibiste. Casi siempre es porque tu framework ya convirtió el cuerpo a JSON antes de verificarlo (por ejemplo, express.json() antes de la ruta del webhook), o porque usas el secreto de otro endpoint o del modo de prueba. En Express, usa express.raw({ type: 'application/json' }) en esa ruta.

¿El webhook de Mercado Pago trae el estado del pago?

No. Trae el tipo, la acción y el id del recurso (data.id). Para saber si el pago se aprobó tienes que consultarlo en /v1/payments/{id}, con el access token de la misma cuenta y del mismo modo (prueba o producción).

Fuentes: la documentación de webhooks de Mercado Pago (mercadopago.com.mx/developers) y de Stripe (docs.stripe.com/webhooks), revisadas el 12 de octubre de 2026.

Sigue leyendo