# Stripe webhook: enviar email, SMS o WhatsApp si falla un pago

> Stripe reintenta una tarjeta rechazada hasta 8 veces en 2 semanas, pero solo avisa por email. Añade WhatsApp y SMS a tus reclamaciones de pago, gratis.

Source: https://unlimitedmessaging.app/es/articles/stripe-payment-notifications-whatsapp-email

Guía Stripe

# Falla una tarjeta en Stripe,  
_tu cliente recibe un WhatsApp._

Un solo webhook escucha invoice.paid, invoice.payment\_failed y customer.subscription.trial\_will\_end, y envía el recibo, el recordatorio de pago o el aviso de fin de prueba por email, WhatsApp o SMS desde tus propias cuentas. Gratis, con el código para Next.js.

[Obtener una clave API gratis](https://app.unlimitedmessaging.app)[Ver la API](https://unlimitedmessaging.app/es/api)

La respuesta corta

## ¿Cómo enviar un email, un WhatsApp o un SMS desde un webhook de Stripe?

Crea un endpoint de webhook, verifica cada evento con stripe.webhooks.constructEvent sobre el cuerpo sin procesar, responde 2xx al momento y envía el mensaje con una llamada POST /message. Stripe envía [customer.subscription.trial\_will\_end](https://docs.stripe.com/billing/subscriptions/webhooks) 3 días antes de que acabe una prueba e invoice.payment\_failed tras cada intento fallido: cada evento da un mensaje.

Stripe ya hace parte del trabajo. Smart Retries reintenta un pago de suscripción fallido, hasta 8 veces en 2 semanas por defecto en las cuentas nuevas, y los emails a clientes de Stripe pueden avisar de un pago fallido, de una prueba que acaba en 7 días o de una tarjeta que caduca. Solo por email.

Un email sobre una tarjeta rechazada se pierde fácilmente. Un WhatsApp o un SMS desde un número que el cliente reconoce se lee, y el enlace a la página de factura alojada le permite pagar en un minuto. Tu webhook decide el canal y el momento: email y WhatsApp en el primer fallo, SMS en los siguientes intentos.

Stripe puede entregar el mismo evento dos veces y en cualquier orden, y reintenta un endpoint en producción durante hasta 3 días si no recibe un 2xx. Tu handler tiene que ser rápido e idempotente: procesa cada event.id una sola vez.

Lee también: [las notificaciones transaccionales por WhatsApp](https://unlimitedmessaging.app/es/articles/notifications), y [cómo evitar un bloqueo de WhatsApp](https://unlimitedmessaging.app/es/articles/avoid-whatsapp-ban).

![Dibujo de la aplicación Unlimited Messaging que muestra una lista de mensajes con su estado (leído, entregado, enviado, en cola), con el titular «Cobros de Stripe por WhatsApp, SMS y email».](https://unlimitedmessaging.app/_next/image?url=%2Fassets%2Farticles%2Fcovers%2Fstripe-payment-notifications-whatsapp-email-es.png&w=3840&q=75)

Definición
Dunning (reclamación de pagos)

Recuperar un pago recurrente fallido: reintentar el cobro y pedir al cliente que actualice su método de pago antes de que se cancele la suscripción.

También llamado recuperación de pagos fallidos o revenue recovery.

[Fuente: Stripe Docs, reintentos de pago automáticos↗](https://docs.stripe.com/billing/revenue-recovery/smart-retries)

Paso a paso

## ¿Cómo se construye, paso a paso?

Cinco pasos en un proyecto Next.js App Router, unos 20 minutos. Cualquier backend funciona igual: verificar, responder 2xx, enviar.

1. 01  
### Reúne los tres secretos  
Tu clave secreta de Stripe, el secreto de firma (whsec\_) de tu endpoint de webhook y una clave API de Unlimited Messaging cuando tengas conectado tu buzón, tu número de WhatsApp o tu móvil Android. Los tres se quedan en el servidor, nunca con el prefijo NEXT\_PUBLIC\_.  
.env.local.env.local  
```  
# .env.local (server only, never NEXT_PUBLIC_)  
STRIPE_SECRET_KEY=sk_test_xxxx  
# Printed by "stripe listen" in dev, or shown on the endpoint in the Dashboard  
STRIPE_WEBHOOK_SECRET=whsec_xxxx  
UM_API_KEY=um_live_xxxx  
```
2. 02  
### Añade el helper notify  
Una función solo de servidor alrededor de POST /message. Omite los canales sin destinatario y marca el mensaje como transaccional, para que pase por delante de cualquier envío masivo que espere en tu cola.  
lib/notify.tsTypeScript  
```  
// lib/notify.ts  
import 'server-only';  
export type Channel = 'EMAIL' | 'WHATSAPP' | 'SMS';  
export async function notify(input: {  
  email?: string;  
  phone?: string; // E.164, e.g. +33612345678  
  channels: Channel[];  
  subject: string;  
  text: string;  
}) {  
  const channels = input.channels  
    .filter((c) => (c === 'EMAIL' ? input.email : input.phone))  
    .map((channel) => ({ channel }));  
  if (channels.length === 0) return null;  
  const res = await fetch('https://api.unlimitedmessaging.app/message', {  
    method: 'POST',  
    headers: {  
      Authorization: `Bearer ${process.env.UM_API_KEY}`,  
      'Content-Type': 'application/json',  
    },  
    body: JSON.stringify({  
      to: { email: input.email, phone: input.phone },  
      channels,  
      subject: input.subject,  
      text: input.text,  
      priority: 'transactional',  
    }),  
  });  
  if (!res.ok) throw new Error(`Unlimited Messaging ${res.status}: ${await res.text()}`);  
  return res.json();  
}  
```
3. 03  
### Crea la ruta del webhook  
Lee el cuerpo sin procesar con req.text(): Stripe firma los bytes exactos, y parsear el JSON antes rompe la verificación. constructEvent también rechaza los eventos más antiguos que su tolerancia por defecto de 5 minutos. Responde 200 al momento y envía dentro de after().  
app/api/stripe/webhook/route.tsRoute Handler  
```  
// app/api/stripe/webhook/route.ts  
import type Stripe from 'stripe';  
import { after } from 'next/server';  
import { stripe, handleStripeEvent } from '@/lib/stripe-notify';  
export async function POST(req: Request) {  
  // The raw body: parsing it as JSON first breaks the signature check  
  const body = await req.text();  
  const signature = req.headers.get('stripe-signature') ?? '';  
  let event: Stripe.Event;  
  try {  
    event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);  
  } catch {  
    return new Response('Invalid signature', { status: 400 });  
  }  
  // Answer Stripe with a 2xx right away, send the messages after the response  
  after(() => handleStripeEvent(event).catch(console.error));  
  return new Response('ok', { status: 200 });  
}  
```
4. 04  
### Asocia cada evento a un mensaje  
invoice.paid envía un recibo por email. invoice.payment\_failed envía email y WhatsApp con el enlace de la factura alojada, y añade el SMS cuando attempt\_count pasa de 1\. customer.subscription.trial\_will\_end llega 3 días antes del fin de la prueba: recupera el cliente para el email y el teléfono.  
lib/stripe-notify.tsTypeScript  
```  
// lib/stripe-notify.ts  
import 'server-only';  
import Stripe from 'stripe';  
import { notify, type Channel } from '@/lib/notify';  
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);  
const money = (amount: number, currency: string) =>  
  new Intl.NumberFormat('en', { style: 'currency', currency }).format(amount / 100);  
export async function handleStripeEvent(event: Stripe.Event) {  
  // Stripe can send the same event twice: skip the ones already handled  
  if (!(await markProcessed(event.id))) return;  
  switch (event.type) {  
    case 'invoice.paid': {  
      const inv = event.data.object;  
      await notify({  
        email: inv.customer_email ?? undefined,  
        channels: ['EMAIL'],  
        subject: 'Payment received',  
        text: `Thanks, we received ${money(inv.amount_paid, inv.currency)}.`,  
      });  
      break;  
    }  
    case 'invoice.payment_failed': {  
      const inv = event.data.object;  
      // First failure: email + WhatsApp. Next retries: SMS as well.  
      const channels: Channel[] =  
        inv.attempt_count > 1 ? ['EMAIL', 'WHATSAPP', 'SMS'] : ['EMAIL', 'WHATSAPP'];  
      await notify({  
        email: inv.customer_email ?? undefined,  
        phone: inv.customer_phone ?? undefined,  
        channels,  
        subject: 'Your payment did not go through',  
        text: `Your payment of ${money(inv.amount_due, inv.currency)} failed. Update your card here: ${inv.hosted_invoice_url}`,  
      });  
      break;  
    }  
    case 'customer.subscription.trial_will_end': {  
      const sub = event.data.object; // sent 3 days before the trial ends  
      const customer = await stripe.customers.retrieve(sub.customer as string);  
      if (customer.deleted) break;  
      await notify({  
        email: customer.email ?? undefined,  
        phone: customer.phone ?? undefined,  
        channels: ['EMAIL', 'WHATSAPP'],  
        subject: 'Your trial ends in 3 days',  
        text: 'Your trial ends in 3 days. Nothing to do if your card is up to date.',  
      });  
      break;  
    }  
  }  
}  
async function markProcessed(eventId: string): Promise<boolean> {  
  // e.g. INSERT INTO stripe_events (id) VALUES ($1) ON CONFLICT DO NOTHING,  
  // then return true only when a row was inserted  
  return true;  
}  
```
5. 05  
### Prueba con la CLI de Stripe y pasa a producción  
stripe listen reenvía los eventos de prueba a localhost y muestra el secreto de firma que debes usar. Dispara un pago fallido, revisa los mensajes y añade tu URL de producción como endpoint en el Dashboard con los tres eventos y su propio secreto whsec\_.  
TerminalStripe CLI  
```  
# Forward test events to your local route (prints a whsec_ secret)  
stripe listen --forward-to localhost:3000/api/stripe/webhook  
# In another terminal: fire a failed payment  
stripe trigger invoice.payment_failed  
```

Vibe coding

## ¿Programas con Cursor, Lovable o Claude Code? Pega este prompt

El prompt le da a tu editor de IA el patrón seguro: verificación de firma sobre el cuerpo sin procesar, 2xx rápido, handler idempotente, mensajes por canal. Te pide tus contactos de prueba antes de enviar.

Prompt para copiar

Añade notificaciones a clientes en los eventos de Stripe a este proyecto Next.js (App Router) con Unlimited Messaging: email, WhatsApp y SMS enviados desde mis propias cuentas.

1. Usa STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET y UM_API_KEY desde .env.local, y añádelas a .env.example. No uses nunca un prefijo NEXT_PUBLIC_.
2. Crea lib/notify.ts (import 'server-only') que exporte notify({ email, phone, channels, subject, text }) y envíe un POST https://api.unlimitedmessaging.app/message con la cabecera Authorization: Bearer UM_API_KEY y el JSON { to: { email, phone }, channels: [{ channel: 'EMAIL' | 'WHATSAPP' | 'SMS' }], subject, text, priority: 'transactional' }. Omite los canales sin destinatario.
3. Crea app/api/stripe/webhook/route.ts: lee el cuerpo sin procesar con req.text(), verifícalo con stripe.webhooks.constructEvent y la cabecera stripe-signature, devuelve 400 si falla, devuelve 200 al momento y procesa el evento dentro de after() de next/server.
4. Gestiona invoice.paid (recibo por email), invoice.payment_failed (email + WhatsApp con hosted_invoice_url, añade SMS cuando attempt_count > 1) y customer.subscription.trial_will_end (recupera el cliente, email + WhatsApp). Guarda event.id en una tabla con una restricción de unicidad y omite los eventos ya procesados.
5. Referencia de la API: https://docs.unlimitedmessaging.app. Webhooks de Stripe: https://docs.stripe.com/webhooks.

Enséñame el diff y los comandos stripe listen / stripe trigger para probar, y pregúntame mi email y mi número de prueba antes de enviar nada.

Las opciones

## ¿Los emails integrados de Stripe o tus propios mensajes por webhook?

Los emails a clientes de Stripe no necesitan código. Un webhook añade los canales y el calendario que elijas. Se combinan bien.

__¿Los emails integrados de Stripe o tus propios mensajes por webhook?__
| [Emails a clientes de Stripe](https://docs.stripe.com/billing/revenue-recovery/customer-emails)Integrados en Billing | [Webhook + API de email](https://resend.com/pricing)Por ejemplo Resend | [Webhook + Twilio](https://www.twilio.com/en-us/sms/pricing)API de SMS | Webhook + Unlimited MessagingEmail, WhatsApp y SMS |                                                   |
| -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------- |
| Canales                                                                                                              | Email                                                                  | Email                                                                  | SMS, WhatsApp con plantillas de Meta               | Email, WhatsApp y SMS en una petición             |
| Pago fallido                                                                                                         | Un email tras cada pago con tarjeta fallido                            | Tu email, tu calendario                                                | Tu SMS, tu calendario                              | Escalar del email a WhatsApp y SMS                |
| Fin de la prueba                                                                                                     | Email de aviso 7 días antes                                            | Evento 3 días antes, tu email                                          | Evento 3 días antes, tu SMS                        | Evento 3 días antes, cualquier canal              |
| Coste de los mensajes                                                                                                | Incluido en Stripe Billing                                             | Gratis hasta 3.000 emails al mes, 100 al día                           | Se cobra por segmento de SMS                       | Plan Free, sin coste por mensaje                  |
| Código                                                                                                               | Ninguno, ajustes del Dashboard                                         | Un handler de webhook                                                  | Un handler de webhook                              | Un handler de webhook                             |
| Remitente                                                                                                            | Stripe, con tu marca                                                   | Tu dominio verificado                                                  | Un número de Twilio                                | Tu propio buzón, tu número de WhatsApp y tu móvil |

Comprobado el 9 de noviembre de 2026 en la documentación de Stripe, resend.com/pricing y twilio.com (enlaces en las fuentes). Puedes mantener los emails de Stripe y añadir WhatsApp o SMS encima.

La letra pequeña

## ¿Cuáles son los límites?

Gratis quita la factura por mensaje. Los webhooks siguen pidiendo cuidado, y WhatsApp y el SMS siguen teniendo sus reglas.

1. 01  
### Los teléfonos en Stripe son texto libre  
customer\_phone y customer.phone guardan lo que se escribió. WhatsApp y el SMS necesitan formato E.164 (+34612345678): normaliza los números al recogerlos, por ejemplo con la recogida de teléfono de Checkout, y omite los canales de teléfono si falta el número.
2. 02  
### Responder 200 primero significa que los reintentos son cosa tuya  
after() envía los mensajes cuando Stripe ya tiene su 2xx, así que Stripe no reenviará si tu envío falla. Para mensajes que no puedes perder, guarda el evento y procésalo desde una cola que reintente.
3. 03  
### Ni duplicados ni orden están garantizados  
Stripe puede entregar un evento más de una vez y no garantiza el orden. Deduplica por event.id, no por created, y recupera el objeto actualizado desde la API cuando importe el estado actual.
4. 04  
### WhatsApp es no oficial: recordatorios sí, campañas no  
Tu número se vincula como un dispositivo, fuera de la plataforma Business de Meta. Un recordatorio de pago a tu propio cliente es esperado; una campaña a todos tus antiguos clientes es el tipo de tráfico que hace que bloqueen un número.
5. 05  
### El SMS necesita un móvil Android encendido  
Los SMS salen de la SIM de tu móvil, con tu tarifa. Android pide confirmación a partir de 30 SMS en 30 minutos, así que la app espacia los envíos a unos 30 cada 30 minutos: de sobra para reclamaciones, no para una campaña masiva.
6. 06  
### Los mensajes del plan Free llevan una firma  
En el plan Free, los mensajes terminan con una línea corta "Sent with UnlimitedMessaging.app", y los archivos multimedia y los webhooks de estado son de pago. Medium y Max quitan la línea.

Cuándo elegir otra cosa

### ¿Cero código, o mucho volumen?

Si te basta el email, activa en el Dashboard los emails de pago fallido y de fin de prueba de Stripe y listo: ningún endpoint que alojar. Si envías miles de SMS al día o necesitas las plantillas oficiales de WhatsApp de Meta, encaja mejor un proveedor como Twilio. Tu propio webhook con Unlimited Messaging encaja con un SaaS que quiere sus reclamaciones de pago en los canales que sus clientes leen, desde su propio número, gratis mientras crece.

FAQ

## Preguntas sobre las notificaciones por webhook de Stripe

Verifica el evento con stripe.webhooks.constructEvent sobre el cuerpo sin procesar, responde 2xx y llama a una API de email. Con Unlimited Messaging, POST /message con el canal EMAIL envía desde tu propio buzón, sin coste por mensaje en el plan Free.

invoice.paid para los recibos, invoice.payment\_failed para las reclamaciones (attempt\_count indica qué intento falló) y customer.subscription.trial\_will\_end para los avisos de fin de prueba, enviado 3 días antes del final. invoice.payment\_action\_required cubre los pagos que necesitan autenticación del cliente.

Sí. En el handler de invoice.payment\_failed, envía POST /message con el canal SMS a invoice.customer\_phone. Con Unlimited Messaging sale de la SIM de tu móvil Android, sin coste por SMS de nuestra parte.

No. Los emails automáticos de Stripe a clientes son solo emails. Las reclamaciones por WhatsApp o SMS pasan por tu propio webhook.

No necesariamente. Puedes mantener el email de Stripe, que enlaza a su página alojada, y añadir un WhatsApp o un SMS. Solo evita enviar dos emails por el mismo fallo.

Casi siempre porque el cuerpo se parseó antes de verificarlo. En un Route Handler del App Router, léelo con await req.text() y pasa esa cadena a constructEvent con la cabecera stripe-signature y el secreto whsec\_ del endpoint correcto: el que muestra stripe listen no es el del Dashboard.

El plan Free de Unlimited Messaging no tiene coste por mensaje en ningún canal y no pide tarjeta; los mensajes llevan una firma corta. Stripe cobra por los pagos y por Billing, no por enviar eventos de webhook.

Fuentes

## Fuentes y referencias

Para verificar por ti mismo la información de este artículo:

1. [01Using webhooks with subscriptions (trial\_will\_end 3 días antes, invoice.payment\_failed)↗Stripe Docs](https://docs.stripe.com/billing/subscriptions/webhooks)
2. [02Receive Stripe events in your webhook endpoint (firmas, 2xx, duplicados, tolerancia de 5 minutos)↗Stripe Docs](https://docs.stripe.com/webhooks)
3. [03Automate payment retries (Smart Retries: hasta 8 reintentos en 2 semanas por defecto)↗Stripe Docs](https://docs.stripe.com/billing/revenue-recovery/smart-retries)
4. [04Automate customer emails (pagos fallidos, aviso 7 días antes del fin de la prueba)↗Stripe Docs](https://docs.stripe.com/billing/revenue-recovery/customer-emails)
5. [05after (estable desde la v15.1.0)↗Next.js Docs](https://nextjs.org/docs/app/api-reference/functions/after)
6. [06route.js (Route Handlers)↗Next.js Docs](https://nextjs.org/docs/app/api-reference/file-conventions/route)
7. [07Pricing (Free: 3.000 emails al mes, 100 al día)↗Resend](https://resend.com/pricing)
8. [08Precios de SMS, por segmento y por país↗Twilio](https://www.twilio.com/en-us/sms/pricing)
9. [09SmsUsageMonitor (umbral de 30 SMS en 30 minutos)↗Android Open Source Project](https://android.googlesource.com/platform/frameworks/opt/telephony/+/refs/heads/main/src/java/com/android/internal/telephony/SmsUsageMonitor.java)
10. [10Referencia de la API: POST /message↗Unlimited Messaging Docs](https://docs.unlimitedmessaging.app)

![Ilian, ingeniero de software senior](https://ilianazz.com/images/iliazz.png)

Escrito por

[Ilian](https://unlimitedmessaging.app/es/authors/ilian) · Ingeniero de software senior

Ingeniero de software senior, funda y dirige varias empresas tecnológicas. Diseña productos en torno a las API de mensajería, la automatización y la integración de la IA, y comparte aquí lo que aprende construyendo.

[Ver el perfil de LinkedIn↗](https://www.linkedin.com/in/ilianAzz/)Actualizado el 9 de noviembre de 2026

![](https://unlimitedmessaging.app/assets/logos/whatsapp.svg)![](https://unlimitedmessaging.app/assets/logos/email.svg)![](https://unlimitedmessaging.app/assets/logos/sms.svg)

## Convierte hoy los pagos fallidos en recordatorios por WhatsApp.

Crea una cuenta gratis, conecta un buzón, un número de WhatsApp o un móvil Android y pega el handler. Sin tarjeta, sin coste por mensaje.

[Empezar gratis→](https://app.unlimitedmessaging.app)

Artículos similares
* [![](https://unlimitedmessaging.app/_next/image?url=%2Fassets%2Farticles%2Fcovers%2Fnotifications-es.png&w=3840&q=75)Confirmaciones de pedido por WhatsApp: la guía 2026Envía confirmaciones de pedidos, alertas y recibos via WhatsApp.](https://unlimitedmessaging.app/es/articles/notifications)
* [![](https://unlimitedmessaging.app/_next/image?url=%2Fassets%2Farticles%2Fcovers%2Fecommerce-order-notifications-whatsapp-sms-es.png&w=3840&q=75)E-commerce: 5 avisos de pedido por WhatsApp y SMS, gratisE-commercePedido confirmado, enviado, en reparto: avisa a tus clientes por WhatsApp, SMS y email desde tus cuentas, sin coste por mensaje.](https://unlimitedmessaging.app/es/articles/ecommerce-order-notifications-whatsapp-sms)
* [![](https://unlimitedmessaging.app/_next/image?url=%2Fassets%2Farticles%2Fcovers%2Femail-whatsapp-single-api-request-es.png&w=3840&q=75)Una petición API, dos canales: email y WhatsApp a la vezEnvía el mismo mensaje por WhatsApp y por email en una llamada, desde tu número y tu buzón, gratis en todos los planes.](https://unlimitedmessaging.app/es/articles/email-whatsapp-single-api-request)
