Saltar al contenido principal

Webhook multicanal

Cada respuesta, en cada canal,
en un solo webhook.

WhatsApp desde tu número, email desde tu buzón, SMS desde tu móvil: un solo endpoint los recibe todos en el mismo sobre JSON firmado. Un handler alimenta tu bandeja, tu agente de IA o tu CRM.

¿Con prisa? Resume este artículo con una IA

La respuesta corta

¿Puede un solo webhook recibir WhatsApp, email y SMS?

Sí. Unlimited Messaging envía cada mensaje entrante de tu número de WhatsApp vinculado, tu buzón conectado y la SIM de tu móvil Android a una sola URL, como el mismo evento, message.received, firmado según Standard Webhooks. El sobre nunca cambia; data.channel.type indica WHATSAPP, EMAIL o SMS. Los webhooks están en los planes de pago Medium y Max.

Montada con proveedores separados, la misma bandeja exige tres integraciones. La Cloud API de Meta envía JSON anidado firmado con X-Hub-Signature-256 tras una verificación hub.challenge. Twilio envía SMS y WhatsApp como parámetros form-encoded firmados con HMAC-SHA1. SendGrid Inbound Parse envía multipart/form-data para un nombre de host cuyo MX has movido. Tres parsers, tres verificaciones de firma, tres ideas distintas de lo que es una conversación.

Aquí los canales son servicios junto a una sola API, y esa API anuncia cada mensaje igual: id, direction, content, sender, sentAt, la conversación (un chat o grupo de WhatsApp, la raíz de un hilo de email, un número SMS) y la cuenta por la que llegó. El email añade subject, html y attachments; WhatsApp añade archivos, grupos, reacciones y ediciones. Las reglas de entrega también son comunes: responder 2xx en menos de 10 segundos, 8 intentos en algo más de 3 días, deduplicar por eventId.

Las respuestas salen desde las mismas identidades. replyToMessageId responde en el mismo chat de WhatsApp o hilo de email; una respuesta SMS indica la SIM. Tus clientes ven tu número y tu dirección, nunca los de un proveedor.

Relacionado: enviar email y WhatsApp en una sola petición a la API y reunir las ventas por WhatsApp y email en una sola bandeja.

Ilustración: dos servidores que intercambian eventos webhook.

Los eventos

¿Cómo es el payload del webhook multicanal?

El mismo evento message.received en cada canal. Lo que cambia es data.channel.type y algunos campos propios del canal.

  1. Común a todos los canales

    type, eventId, occurredAt; data.message con id, externalId, direction, fromMe, content, sentAt, receivedAt, sender, senderId; data.channel con type, account (id, name) y conversation (id, isGroup, address).

  2. WHATSAPP

    Archivos (mediaUrl, mediaType, mimeType), ubicaciones, respuestas citadas (replyTo), grupos (participant, conversation.participants), más message.reaction, message.edited y message.deleted.

  3. EMAIL

    subject, html (no fiable, hay que sanearlo), attachments (hasta 20 por email), conversation.id igual a la raíz del hilo y replyTo al mensaje respondido.

  4. SMS

    Solo texto. conversation.id es null, así que agrupa por sender. Mensajes recibidos después de iniciar sesión en la app Android.

  5. Alrededor de los mensajes

    message.sent y message.status siguen lo que envías en los tres canales; account.status_changed avisa de un número, buzón o SIM que se cae.

Tres canales, un sobreJSON
// WhatsApp
{ "type": "message.received", "eventId": "whd_6f1c...", "occurredAt": "2026-11-09T10:00:01.000Z",
  "data": {
    "message": { "id": "msg_0e55...", "direction": "IN", "content": "Is my order ready?",
                 "sender": "+447700900123", "senderName": "Lea", "sentAt": "2026-11-09T10:00:00.000Z" },
    "channel": { "type": "WHATSAPP", "account": { "id": "acc_373b...", "name": "Support" },
                 "conversation": { "id": "[email protected]", "isGroup": false,
                                   "address": "+447700900123" } } } }

// Email: same envelope, plus subject, html and attachments
{ "type": "message.received", "eventId": "whd_9a4f...", "occurredAt": "2026-11-09T14:05:12.000Z",
  "data": {
    "message": { "id": "msg_3f8a...", "direction": "IN", "content": "Signed quote attached.",
                 "sender": "[email protected]", "subject": "Re: Quote #1042",
                 "html": "<p>Signed quote attached.</p>", "attachments": [ { "filename": "quote.pdf" } ] },
    "channel": { "type": "EMAIL", "account": { "id": "acc_8b2e...", "name": "[email protected]" },
                 "conversation": { "id": "[email protected]", "isGroup": false,
                                   "address": "[email protected]" } } } }

// SMS: text only, no conversation id
{ "type": "message.received", "eventId": "whd_2b9e...", "occurredAt": "2026-11-09T08:42:06.000Z",
  "data": {
    "message": { "id": "msg_7d2c...", "direction": "IN", "content": "Running 10 min late, sorry!",
                 "sender": "+447700900456", "sentAt": "2026-11-09T08:42:03.000Z" },
    "channel": { "type": "SMS", "account": { "id": "acc_5e8d...", "name": "Front desk SIM" },
                 "conversation": { "id": null, "isGroup": false, "address": "+447700900456" } } } }
Cuerpos message.received recortados, a partir del esquema publicado, uno por canal. El evento completo lleva todos los campos listados a la izquierda.

Puesta en marcha

¿Cómo escribir un solo handler para todos los canales?

Vincula los tres canales una vez, registra un endpoint y diferencia solo donde un canal cambia de verdad.

  1. 01

    Vincula tus canales

    Escanea un código QR para WhatsApp, conecta tu buzón por IMAP/SMTP, instala la app Android para el SMS. Cada uno se convierte en una cuenta.

  2. 02

    Registra un solo endpoint

    POST /webhooks con tu URL y tus eventTypes, o añádelo en la página Webhooks del panel. Sin accountIds cubre todas las cuentas, incluso las futuras.

  3. 03

    Verifica una vez, diferencia al final

    Una verificación de firma, un upsert por eventId, una fila en la bandeja. Solo cambia la llamada de respuesta: replyToMessageId para WhatsApp y email, la SIM para el SMS.

POST /webhookscurl
curl https://api.unlimitedmessaging.app/webhooks \
  -H "Authorization: Bearer $UM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/um",
    "eventTypes": ["message.received", "message.status", "account.status_changed"],
    "description": "Every reply, every channel"
  }'

# No accountIds: the endpoint covers every WhatsApp number, mailbox and SIM you have,
# including ones linked later. The response carries "secret": "whsec_..." once.
No existe la opción «todos los eventos»: indica los tipos que quieres. Un tipo añadido más tarde nunca se activa en los endpoints existentes; envía la lista completa con PATCH /webhooks/{id} para añadirlo.
app/api/webhooks/um/route.tsNext.js route handler
// app/api/webhooks/um/route.ts (Next.js App Router)
import crypto from 'node:crypto';
import { UnlimitedMessagingApiClient } from '@unlimited-messaging/sdk';

const um = new UnlimitedMessagingApiClient({ token: process.env.UM_API_KEY! });
const key = Buffer.from(process.env.UM_WEBHOOK_SECRET!.replace(/^whsec_/, ''), 'base64');

// Standard Webhooks: HMAC-SHA256 of "id.timestamp.rawBody", base64.
function verify(raw: string, id: string, ts: string, header: string) {
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = crypto.createHmac('sha256', key).update(`${id}.${ts}.${raw}`).digest('base64');
  return header.split(' ').some((entry) => {
    const sig = entry.startsWith('v1,') ? entry.slice(3) : '';
    return sig.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  });
}

export async function POST(req: Request) {
  const raw = await req.text();
  const h = req.headers;
  if (!verify(raw, h.get('webhook-id') ?? '', h.get('webhook-timestamp') ?? '',
    h.get('webhook-signature') ?? '')) {
    return new Response('bad signature', { status: 401 });
  }

  const event = JSON.parse(raw);
  if (event.type !== 'message.received') return new Response(null, { status: 200 });

  const { message: m, channel } = event.data;
  // One inbox row, whatever the channel (upsert on eventId: delivery is at least once).
  await inbox.upsert({
    eventId: event.eventId,
    channel: channel.type,                       // WHATSAPP | EMAIL | SMS
    from: m.sender ?? m.senderId,                // +E164 or email address
    thread: channel.conversation.id ?? m.sender, // SMS has no conversation id
    subject: m.subject ?? null,
    text: m.content,
    at: m.sentAt,                                // order on this, not on arrival
  });

  const answer = await draftReply(m.content);    // your LLM call, or null for a human
  if (answer) {
    if (channel.type === 'SMS') {
      await um.messages.messageSend({
        to: { phone: m.sender },
        channels: [{ channel: 'SMS', accountId: channel.account.id }],
        text: answer,
      });
    } else {
      // WhatsApp and email: same chat or same thread, same number or mailbox.
      await um.messages.messageSend({ replyToMessageId: m.id, text: answer });
    }
  }
  return new Response(null, { status: 200 }); // within 10 s; queue slow work
}
inbox y draftReply son tuyos (una tabla, una llamada a un LLM). La verificación sigue el esquema Standard Webhooks documentado, igual para todos los canales.

Vibe coding

¿Programas con Cursor, Claude Code, Lovable o Bolt? Pega esto

Contexto suficiente para que tu editor con IA construya toda la ruta de la bandeja unificada de una vez.

Prompt para copiar
Add a unified inbox to this app with Unlimited Messaging: WhatsApp (my number), email (my mailbox) and SMS (my Android phone) all arrive on ONE webhook.

1. Create a POST route at /api/webhooks/um that reads the RAW body.
2. Verify the Standard Webhooks signature: key = base64-decode(UM_WEBHOOK_SECRET without "whsec_"); expected = base64(HMAC-SHA256(key, `${webhook-id}.${webhook-timestamp}.${rawBody}`)); accept if any space-separated entry of the webhook-signature header equals `v1,${expected}` (constant-time compare); reject timestamps more than 5 minutes off; return 401 otherwise.
3. On type "message.received", upsert one row keyed on body.eventId: channel = data.channel.type (WHATSAPP | EMAIL | SMS), from = data.message.sender ?? data.message.senderId, thread = data.channel.conversation.id ?? data.message.sender, subject = data.message.subject, text = data.message.content, at = data.message.sentAt (order on this, never on arrival).
4. Build an inbox page listing threads across channels with a channel badge.
5. Replies: install @unlimited-messaging/sdk, read UM_API_KEY from env. WhatsApp and email: `client.messages.messageSend({ replyToMessageId: data.message.id, text })`. SMS: `client.messages.messageSend({ to: { phone: sender }, channels: [{ channel: 'SMS', accountId: data.channel.account.id }], text })`.
6. Return 200 within 10 seconds; queue slow work. Put UM_API_KEY and UM_WEBHOOK_SECRET in .env and .env.example.

Docs: https://docs.unlimitedmessaging.app (Webhooks). Register with POST https://api.unlimitedmessaging.app/webhooks and eventTypes ["message.received", "message.status", "account.status_changed"].

Lo que se construye

¿Qué puedes construir con un solo webhook para todos los canales?

Tres proyectos que en otros sitios son tres integraciones y aquí un solo handler.

Una bandeja de soporte multicanal

Una lista de conversaciones, una etiqueta por canal, respuestas enviadas desde tu número o tu dirección en el mismo hilo. Añade message.status para mostrar entregado y leído cuando el canal lo informa.

Un contestador con IA

Pasa content a Claude o GPT con el historial de la conversación, devuelve la respuesta por el canal de origen y pasa a una persona ante palabras clave o dudas.

Sincronización con el CRM

Asocia cada mensaje al contacto por teléfono o dirección de email, sea cual sea el canal, con sentAt para la cronología.

  1. Cambio de canal

    ¿Sin respuesta en WhatsApp tras un día? Insiste por email o SMS; el mismo POST /message puede enviar por tres canales a la vez.

  2. Supervisión

    account.status_changed en los tres canales te avisa cuando un número, un buzón o un móvil deja de reenviar.

  3. Flujos sin código

    Apunta el webhook a n8n, Make o Zapier y ramifica por data.channel.type.

La tabla

Una bandeja, tres caminos: Twilio, una pila propia o Unlimited Messaging

Lo que hace falta para recibir respuestas de WhatsApp, email y SMS en el mismo código.

Una bandeja, tres caminos: Twilio, una pila propia o Unlimited Messaging
Twilio + SendGridUn proveedor, varios productosMeta + servicio de parsing + proveedor SMSHecho a manoUnlimited MessagingUn webhook, planes de pago
Formatos de payloadForm-encoded para SMS y WhatsApp, multipart para emailJSON de Meta, formato del proveedor, multipart o JSON para emailUn solo sobre JSON
Verificaciones de firmaX-Twilio-Signature (HMAC-SHA1), más la protección propia del endpoint de parsingX-Hub-Signature-256 para Meta, una por cada otro proveedorUna sola verificación Standard Webhooks
Identidades que ven tus clientesNúmeros alquilados a Twilio, un remitente de WhatsApp registradoUn número trasladado a la Cloud API, un número SMS alquiladoTu propio número, buzón y SIM
Configuración antes del primer mensajeCompra de número, registro del remitente de WhatsApp, MXApp de Meta y portfolio comercial, MX, alquiler de númeroEscanear un QR, contraseña del buzón, app Android
Coste por mensaje0,0083 $ por segmento SMS recibido en EE. UU., 0,005 $ por mensaje de WhatsApp más tarifas de MetaTarifas de Meta a partir de 1.000 respuestas de servicio por número al mes, coste por SMSNinguno; webhooks con Medium o Max
Estatus oficialWhatsApp Business Platform oficialWhatsApp Business Platform oficialWhatsApp mediante el protocolo no oficial de dispositivos vinculados

Comprobado el 9 de noviembre de 2026 en las páginas de Twilio (webhooks, precios de SMS y WhatsApp), la documentación de Meta (webhooks, precios) y la guía de Inbound Parse de SendGrid (enlaces en las fuentes).

La letra pequeña

¿Qué límites tiene un webhook unificado sobre tus propios canales?

Un solo formato elimina el código de pegamento. Cada canal conserva su naturaleza.

  1. Los webhooks son de pago

    Enviar por los tres canales es gratis en todos los planes, y los mensajes recibidos aparecen en la bandeja de la aplicación web. Enviarlos a tu servidor requiere Medium (19 € al mes, o 17 € al mes con pago anual) o Max.

  2. WhatsApp es un dispositivo vinculado

    El número se vincula como WhatsApp Web, con un protocolo no oficial: el spam hace que lo restrinjan, y WhatsApp cierra la sesión de los dispositivos vinculados si el móvil principal pasa 14 días sin conexión.

  3. El SMS necesita un móvil Android

    La SIM reenvía a través de la app Android gratis (8.0 o posterior), que debe seguir encendida y conectada. Solo texto, y solo los SMS recibidos después de iniciar sesión.

  4. El email lee la bandeja de entrada

    Por ahora solo buzones IMAP (Gmail con contraseña de aplicación, tu dominio, la mayoría de proveedores); todavía sin Outlook ni Microsoft 365. El correo que va a spam o a otras carpetas no se ve.

  5. No todos los campos existen en todos los canales

    El SMS no tiene identificador de conversación ni acuse de lectura, el email no tiene reacciones, WhatsApp no tiene asunto. Programa sobre los campos comunes y trata el resto como opcional.

FAQ

Preguntas sobre el webhook multicanal

Ilian, ingeniero de software senior

Escrito por

· 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 LinkedInActualizado el

Tres canales, un solo handler.

Crea una cuenta, vincula tu WhatsApp, tu buzón y tu móvil Android, y activa los webhooks con Medium o Max. Enviar sigue siendo gratis en todos los planes.