Webhook de WhatsApp

Recibe en tu sistema, al momento, todo lo que pasa en tus números de WhatsApp: lo que te escriben, lo que se envía, si se entregó y se leyó, y quién lleva cada conversación.

Si conectas WhatsApp con tu CRM, tu programa de citas o tu ERP, no hace falta que preguntes cada minuto si hay algo nuevo: te lo enviamos nosotros. Cada vez que pasa algo en uno de tus números, tu servidor recibe un aviso (un evento) en la dirección que nos indiques, firmado para que sepas que viene de MundoSMS.

Aquí tienes cómo activarlo, qué llega, cómo comprobar la firma (con código listo para PHP, Python y Node.js) y qué hacemos si tu servidor no contesta. Los ejemplos de cada evento son entregas reales de nuestra plataforma, con datos de prueba.

Cómo se activa

  1. En el panel, entra en WhatsApp · Números y abre el Detalle del número.
  2. En «Destino de este número», elige + Nuevo destino…. Ponle un nombre, la URL de tu servidor (tiene que ser https) y marca los eventos que quieres recibir.
  3. Al guardar, el panel te enseña el secreto de la firma (empieza por whsec_). Cópialo: solo se enseña esa vez. Si lo pierdes, genera otro desde la misma ventana con «Nuevo secreto».
  4. Pulsa Probar: tu servidor recibe un evento prueba y el panel te dice qué contestó.

Un mismo destino puede servir a todos tus números: cada evento dice siempre a qué número corresponde. Si en vez de un webhook (o además) prefieres un correo cuando un cliente espera respuesta de una persona, se configura en la misma ventana.

Cómo llega cada evento

Una petición POST a tu URL, con el evento en JSON (UTF-8) en el cuerpo y estas cabeceras:

CabeceraQué lleva
X-MundoSMS-Signaturet=<segundos unix>,v1=<firma>: la firma del cuerpo (ver «Cómo comprobar la firma»).
X-MundoSMS-EventEl tipo de evento, por ejemplo mensaje.recibido.
X-MundoSMS-Event-IdEl identificador único del evento (evt_…), el mismo que el campo id del cuerpo.
Content-Typeapplication/json; charset=utf-8
User-AgentMundoSMS-Webhooks/1
  • Contesta con cualquier código 2xx en menos de 10 segundos. Cualquier otra respuesta, o ninguna en ese plazo, cuenta como fallo y se reintenta. Lo mejor es guardar el evento y procesarlo después.
  • No seguimos redirecciones: la URL tiene que ser la definitiva.
  • Solo enviamos a direcciones https públicas.

Así llega un evento mensaje.recibido, con sus cabeceras:

POST /tu-webhook HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: MundoSMS-Webhooks/1
X-MundoSMS-Signature: t=1792887260,v1=5a48a631280e0eb299b42a842ef51e4395d8a151d844aa0bb5546c30b6d8e501
X-MundoSMS-Event: mensaje.recibido
X-MundoSMS-Event-Id: evt_01a1706884e670c2bed3f7a269e88d0b

{
  "id": "evt_01a1706884e670c2bed3f7a269e88d0b",
  "tipo": "mensaje.recibido",
  "creado": "2026-10-25T02:14:20.390+02:00",
  "cuenta": {
    "usuarios_sid": 12345
  },
  "numero": {
    "id": 1,
    "telefono": "+34 910 00 00 00",
    "phone_number_id": "106540352242922",
    "waba_id": "102290129340398"
  },
  "datos": {
    "mensaje_id": 1,
    "wamid": "wamid.HBgLMzQ2MDAwMDAwMDEVAgASGBQzQUVGRTY0RDQ2",
    "contacto": "34600000001",
    "nombre_contacto": "Ana Pérez",
    "fecha": "2026-10-25T02:14:20.390+02:00",
    "responde_a": null,
    "historial": false,
    "conversacion": {
      "id": 1,
      "contacto": "34600000001",
      "estado": "abierta",
      "asignado_tipo": null,
      "asignado_sid": null
    },
    "tipo": "texto",
    "texto": "Hola, ¿tenéis mesa para 4 esta noche?"
  }
}

Cómo comprobar la firma

Comprueba siempre la firma antes de hacer caso a un evento: es lo que garantiza que lo enviamos nosotros y que nadie lo ha cambiado por el camino.

  1. De la cabecera X-MundoSMS-Signature saca t y v1.
  2. Calcula HMAC-SHA256 con tu secreto como clave sobre el texto t + . + el cuerpo tal cual llegó (los bytes, sin decodificar el JSON ni volver a formatearlo), y pásalo a hexadecimal en minúsculas.
  3. Compáralo con v1 con una comparación de tiempo constante.
  4. Rechaza el evento si t se aleja más de 5 minutos de tu hora: así un evento capturado no se puede volver a enviar más tarde. Ten el reloj de tu servidor en hora (NTP).

Estos receptores hacen todo eso y responden enseguida. Son completos y no necesitan librerías externas. Los tres están probados contra entregas reales de nuestra plataforma, incluidas las manipuladas a propósito.

PHP

<?php
// Receptor del webhook de WhatsApp de MundoSMS: comprueba la firma y responde enseguida.
// El secreto (whsec_...) es el que te enseña el panel al crear el destino; aquí se lee de la variable de entorno
// MUNDOSMS_WEBHOOK_SECRETO.

function mundosms_firma_valida(string $cuerpo, string $cabecera, string $secreto, int $tolerancia = 300): bool
{
    if ($secreto === '') {
        return false;
    }
    $partes = array();
    foreach (explode(',', $cabecera) as $trozo) {
        $kv = explode('=', trim($trozo), 2);
        if (count($kv) === 2) {
            $partes[$kv[0]] = $kv[1];
        }
    }
    if (!isset($partes['t'], $partes['v1']) || !ctype_digit($partes['t'])) {
        return false;
    }
    if (abs(time() - (int) $partes['t']) > $tolerancia) {
        return false; // demasiado vieja o del futuro: puede ser un reenvío malicioso
    }
    $esperada = hash_hmac('sha256', $partes['t'] . '.' . $cuerpo, $secreto);
    return hash_equals($esperada, $partes['v1']);
}

$cuerpo = file_get_contents('php://input'); // el cuerpo TAL CUAL llega: no lo decodifiques antes de comprobarlo
$firma = $_SERVER['HTTP_X_MUNDOSMS_SIGNATURE'] ?? '';
if (!mundosms_firma_valida($cuerpo, $firma, (string) getenv('MUNDOSMS_WEBHOOK_SECRETO'))) {
    http_response_code(401);
    exit;
}
$evento = json_decode($cuerpo, true);
// Puede llegar repetido (reintentos): si ya procesaste $evento['id'], responde 200 y no hagas nada más.
// Guárdalo y procésalo después: si no respondes en 10 segundos, se da por fallido y se reintenta.
http_response_code(200);
echo 'ok';

Python

# Receptor del webhook de WhatsApp de MundoSMS: comprueba la firma y responde enseguida (solo la biblioteca estándar).
# El secreto (whsec_...) es el que te enseña el panel al crear el destino; aquí se lee de la variable de entorno
# MUNDOSMS_WEBHOOK_SECRETO.
import hashlib
import hmac
import json
import os
import time
from http.server import BaseHTTPRequestHandler, HTTPServer


def firma_valida(cuerpo: bytes, cabecera: str, secreto: str, tolerancia: int = 300) -> bool:
    if not secreto:
        return False
    partes = dict(p.strip().split("=", 1) for p in (cabecera or "").split(",") if "=" in p)
    t, v1 = partes.get("t", ""), partes.get("v1", "")
    if not (t.isascii() and t.isdigit()) or abs(time.time() - int(t)) > tolerancia:
        return False  # demasiado vieja o del futuro: puede ser un reenvío malicioso
    esperada = hmac.new(secreto.encode(), t.encode() + b"." + cuerpo, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperada.encode(), v1.encode())


class Receptor(BaseHTTPRequestHandler):
    def do_POST(self):
        cuerpo = self.rfile.read(int(self.headers.get("Content-Length") or 0))  # TAL CUAL llega
        if not firma_valida(cuerpo, self.headers.get("X-MundoSMS-Signature", ""), os.environ.get("MUNDOSMS_WEBHOOK_SECRETO", "")):
            self.send_response(401)
            self.end_headers()
            return
        evento = json.loads(cuerpo)
        # Puede llegar repetido (reintentos): si ya procesaste evento["id"], responde 200 y no hagas nada más.
        # Guárdalo y procésalo después: si no respondes en 10 segundos, se da por fallido y se reintenta.
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"ok")


if __name__ == "__main__":
    HTTPServer(("0.0.0.0", int(os.environ.get("PUERTO", "8080"))), Receptor).serve_forever()

Node.js

// Receptor del webhook de WhatsApp de MundoSMS: comprueba la firma y responde enseguida (solo módulos de Node).
// El secreto (whsec_...) es el que te enseña el panel al crear el destino; aquí se lee de la variable de entorno
// MUNDOSMS_WEBHOOK_SECRETO.
const crypto = require("crypto");
const http = require("http");

function firmaValida(cuerpo, cabecera, secreto, tolerancia = 300) {
  if (!secreto) return false;
  const partes = {};
  for (const trozo of String(cabecera || "").split(",")) {
    const i = trozo.indexOf("=");
    if (i > 0) partes[trozo.slice(0, i).trim()] = trozo.slice(i + 1).trim();
  }
  const t = partes.t || "", v1 = partes.v1 || "";
  if (!/^\d+$/.test(t) || Math.abs(Date.now() / 1000 - Number(t)) > tolerancia) {
    return false; // demasiado vieja o del futuro: puede ser un reenvío malicioso
  }
  const esperada = Buffer.from(crypto.createHmac("sha256", secreto).update(t + ".").update(cuerpo).digest("hex"));
  const recibida = Buffer.from(v1);
  return esperada.length === recibida.length && crypto.timingSafeEqual(esperada, recibida);
}

http.createServer((req, res) => {
  const trozos = [];
  req.on("data", (d) => trozos.push(d));
  req.on("end", () => {
    const cuerpo = Buffer.concat(trozos); // TAL CUAL llega: no lo decodifiques antes de comprobarlo
    if (!firmaValida(cuerpo, req.headers["x-mundosms-signature"], process.env.MUNDOSMS_WEBHOOK_SECRETO)) {
      res.writeHead(401).end();
      return;
    }
    const evento = JSON.parse(cuerpo);
    // Puede llegar repetido (reintentos): si ya procesaste evento.id, responde 200 y no hagas nada más.
    // Guárdalo y procésalo después: si no respondes en 10 segundos, se da por fallido y se reintenta.
    res.writeHead(200).end("ok");
  });
}).listen(Number(process.env.PUERTO || 8080));
Si usas un framework, comprueba la firma con el cuerpo CRUDO: si el framework ya ha decodificado el JSON, la firma no cuadrará. En Express, por ejemplo, declara la ruta con express.raw({ type: "application/json" }) y pasa req.body (un Buffer) a firmaValida; en Flask, usa request.get_data(); en Laravel, $request->getContent().

Reintentos, duplicados y orden

Si tu servidor no contesta con un 2xx, volvemos a intentarlo pasados 1 minuto, 5 minutos, 15 minutos, 1 hora, 3 horas, 6 horas y 12 horas: ocho intentos en total, durante unas 22 horas. Después el evento queda como no entregado, y desde el panel (ventana del destino, «Últimas entregas») lo puedes ver y reenviar.

Si un destino pasa 24 horas sin ningún 2xx y acumula 20 fallos seguidos, lo desactivamos para no insistir contra un servidor caído. Los eventos que lleguen mientras tanto no se pierden: se guardan retenidos y salen en cuanto reactivas el destino en el panel. Si el destino tiene correo, te avisamos.

  • Duplicados: con los reintentos, un mismo evento puede llegarte más de una vez (por ejemplo, si lo procesaste pero tu respuesta no nos llegó). Guarda el id de los que ya procesaste y, si se repite, contesta 200 sin hacer nada más.
  • Orden: no está garantizado. Un mensaje con foto, por ejemplo, sale cuando la foto ya está guardada y puede adelantar al anterior. Para ordenar, usa datos.fecha (cuándo pasó) o creado (cuándo se generó el evento).
  • El historial de entregas se guarda 30 días.

Lo que tienen en común todos los eventos

CampoQué es
idIdentificador único del evento (evt_…). Úsalo para no procesar dos veces el mismo.
tipoEl tipo de evento (tabla de abajo).
creadoCuándo se generó el evento, en ISO 8601 con zona horaria.
cuenta.usuarios_sidTu número de cuenta en MundoSMS (o el de la subcuenta).
numeroEl número de WhatsApp al que corresponde: id (el del panel), telefono, phone_number_id y waba_id (los identificadores de Meta).
datosLo propio de cada tipo de evento.

Los teléfonos de los contactos van en formato internacional, sin + ni espacios (34600000001). Los importes de Meta van en euros (coste_meta_eur) y los de MundoSMS, en créditos (creditos).

Eventos

TipoCuándo llegaLo principal de datos
mensaje.recibidoTe escribe un contacto. También los mensajes antiguos que se importan al conectar un número que ya usabas en la app del móvil, con historial: true. mensaje_id, wamid (el identificador de WhatsApp), contacto, nombre_contacto, fecha, tipo y su contenido (ver «Tipos de mensaje»), responde_a (el wamid del mensaje que cita, si cita alguno) y conversacion.
mensaje.enviadoSale un mensaje de tu número: desde el panel, la API, un envío masivo, la centralita, el agente IA o la app del móvil. origen (panel, api, campana, centralita, bot = agente IA, app = la app del móvil), autor (tipo, sid y nombre: quién lo escribió), categoria de Meta, creditos, coste_meta_eur y el contenido. Si es una plantilla, tipo: "plantilla" con su nombre, idioma y variables.
mensaje.estadoCambia el estado de un mensaje enviado. estado: sent (enviado), delivered (entregado), read (leído), failed (Meta no pudo entregarlo) o rechazado (Meta no lo aceptó al enviarlo). Además, fecha, error (codigo y mensaje de Meta) y precio (si Meta lo cobra, el tipo de precio y la categoría).
conversacion.abiertaEmpieza una conversación nueva con un contacto. conversacion (id, contacto, estado, asignado_tipo, asignado_sid) y motivo: mensaje_del_cliente o mensaje_desde_la_app.
conversacion.asignadaCambia quién la lleva: el agente IA (asignado_tipo: "ia") o una persona ("usuario"). conversacion y motivo: la_atiende_el_agente_ia, persona_coge, persona_contesta, persona_contesta_desde_la_app o devuelta_al_agente_ia.
conversacion.paso_a_personaEl agente IA pide que la conversación la siga una persona (porque el cliente lo ha pedido o porque el agente no puede resolverlo). conversacion (con asignado_tipo: "usuario" y todavía sin persona) y motivo.
conversacion.cerradaSe cierra la conversación. conversacion y motivo: cerrada_por_persona o inactividad.
contacto.bajaUn contacto pide no recibir más mensajes: escribe solo «BAJA», «STOP» o una palabra parecida. Desde ese momento queda fuera de tus envíos masivos. contacto, fuente (palabra) y la palabra que escribió.
pruebaPulsas «Probar» en el panel. Llega siempre, aunque no lo hayas marcado. mensaje.

Un ejemplo de cada uno

mensaje.recibido — Te escribe un contacto
{
  "id": "evt_01a1706884e670c2bed3f7a269e88d0b",
  "tipo": "mensaje.recibido",
  "creado": "2026-10-25T02:14:20.390+02:00",
  "cuenta": {
    "usuarios_sid": 12345
  },
  "numero": {
    "id": 1,
    "telefono": "+34 910 00 00 00",
    "phone_number_id": "106540352242922",
    "waba_id": "102290129340398"
  },
  "datos": {
    "mensaje_id": 1,
    "wamid": "wamid.HBgLMzQ2MDAwMDAwMDEVAgASGBQzQUVGRTY0RDQ2",
    "contacto": "34600000001",
    "nombre_contacto": "Ana Pérez",
    "fecha": "2026-10-25T02:14:20.390+02:00",
    "responde_a": null,
    "historial": false,
    "conversacion": {
      "id": 1,
      "contacto": "34600000001",
      "estado": "abierta",
      "asignado_tipo": null,
      "asignado_sid": null
    },
    "tipo": "texto",
    "texto": "Hola, ¿tenéis mesa para 4 esta noche?"
  }
}
mensaje.recibido — Te manda una foto con pie (con su enlace de descarga)
{
  "id": "evt_01a1706884e670c2bed3f7a3abf4c793",
  "tipo": "mensaje.recibido",
  "creado": "2026-10-25T02:14:20.390+02:00",
  "cuenta": {
    "usuarios_sid": 12345
  },
  "numero": {
    "id": 1,
    "telefono": "+34 910 00 00 00",
    "phone_number_id": "106540352242922",
    "waba_id": "102290129340398"
  },
  "datos": {
    "mensaje_id": 2,
    "wamid": "wamid.HBgLMzQ2MDAwMDAwMDEVAgASGBQzQUY4QTE3RjBC",
    "contacto": "34600000001",
    "nombre_contacto": "Ana Pérez",
    "fecha": "2026-10-25T02:14:20.390+02:00",
    "responde_a": null,
    "historial": false,
    "conversacion": {
      "id": 1,
      "contacto": "34600000001",
      "estado": "abierta",
      "asignado_tipo": null,
      "asignado_sid": null
    },
    "tipo": "imagen",
    "texto": "Esta es la sala que vimos",
    "adjunto": {
      "id": 1,
      "mime": "image/jpeg",
      "nombre": null,
      "bytes": 10244,
      "sha256": "2078274b743c84b46234593c4c5200ff38a392bd9cea81595eb10ea20745caa4",
      "guardado": true,
      "url": "https://wa.mundosms.es/adjuntos/1?e=1795479260&f=9b60f201d4d104ec37443034dae3f20e",
      "caduca": "2026-11-24T01:14:20.390+01:00",
      "motivo": null
    }
  }
}
mensaje.enviado — Una persona contesta desde el panel
{
  "id": "evt_01a1706884ed77bdbc76b06b42595880",
  "tipo": "mensaje.enviado",
  "creado": "2026-10-25T02:14:20.397+02:00",
  "cuenta": {
    "usuarios_sid": 12345
  },
  "numero": {
    "id": 1,
    "telefono": "+34 910 00 00 00",
    "phone_number_id": "106540352242922",
    "waba_id": "102290129340398"
  },
  "datos": {
    "mensaje_id": 3,
    "wamid": "wamid.WH1",
    "contacto": "34600000001",
    "fecha": "2026-10-25T02:14:20.397+02:00",
    "origen": "panel",
    "autor": {
      "tipo": "usuario",
      "sid": "4021",
      "nombre": "Marta"
    },
    "historial": false,
    "categoria": "service",
    "coste_meta_eur": 0.0,
    "creditos": 0.0,
    "tipo": "texto",
    "texto": "¡Hola, Ana! Sí, a las 21:30 tenemos mesa para cuatro."
  }
}
mensaje.enviado — Sale una plantilla
{
  "id": "evt_01a1706884f577f3b1e2b776f7d0dbd6",
  "tipo": "mensaje.enviado",
  "creado": "2026-10-25T02:14:20.405+02:00",
  "cuenta": {
    "usuarios_sid": 12345
  },
  "numero": {
    "id": 1,
    "telefono": "+34 910 00 00 00",
    "phone_number_id": "106540352242922",
    "waba_id": "102290129340398"
  },
  "datos": {
    "mensaje_id": 4,
    "wamid": "wamid.WH2",
    "contacto": "34600000002",
    "fecha": "2026-10-25T02:14:20.405+02:00",
    "origen": "panel",
    "autor": {
      "tipo": "usuario",
      "sid": "4021",
      "nombre": "Marta"
    },
    "historial": false,
    "categoria": "utility",
    "coste_meta_eur": 0.0166,
    "creditos": 1.0,
    "tipo": "plantilla",
    "plantilla": {
      "nombre": "recordatorio_reserva",
      "idioma": "es",
      "variables": [
        "Luis",
        "sábado a las 14:00"
      ]
    }
  }
}
mensaje.estado — Meta confirma la entrega
{
  "id": "evt_01a1706884ef72df8a2750ca9171b299",
  "tipo": "mensaje.estado",
  "creado": "2026-10-25T02:14:20.399+02:00",
  "cuenta": {
    "usuarios_sid": 12345
  },
  "numero": {
    "id": 1,
    "telefono": "+34 910 00 00 00",
    "phone_number_id": "106540352242922",
    "waba_id": "102290129340398"
  },
  "datos": {
    "mensaje_id": 3,
    "wamid": "wamid.WH1",
    "contacto": "34600000001",
    "estado": "delivered",
    "fecha": "2026-10-25T02:14:23.000+02:00",
    "error": null,
    "precio": {
      "facturable": false,
      "tipo": "free_customer_service",
      "categoria": "service"
    },
    "coste_meta_eur": 0.0
  }
}
mensaje.estado — Meta no puede entregar un mensaje
{
  "id": "evt_01a1706884f7765287acb9a5d874492d",
  "tipo": "mensaje.estado",
  "creado": "2026-10-25T02:14:20.407+02:00",
  "cuenta": {
    "usuarios_sid": 12345
  },
  "numero": {
    "id": 1,
    "telefono": "+34 910 00 00 00",
    "phone_number_id": "106540352242922",
    "waba_id": "102290129340398"
  },
  "datos": {
    "mensaje_id": 4,
    "wamid": "wamid.WH2",
    "contacto": "34600000002",
    "estado": "failed",
    "fecha": "2026-10-25T02:14:22.000+02:00",
    "error": {
      "codigo": 131026,
      "mensaje": "Message undeliverable"
    },
    "precio": null,
    "coste_meta_eur": 0.0166
  }
}
conversacion.abierta — Empieza una conversación
{
  "id": "evt_01a1706884e670c2bed3f7a169cc7426",
  "tipo": "conversacion.abierta",
  "creado": "2026-10-25T02:14:20.390+02:00",
  "cuenta": {
    "usuarios_sid": 12345
  },
  "numero": {
    "id": 1,
    "telefono": "+34 910 00 00 00",
    "phone_number_id": "106540352242922",
    "waba_id": "102290129340398"
  },
  "datos": {
    "conversacion": {
      "id": 1,
      "contacto": "34600000001",
      "estado": "abierta",
      "asignado_tipo": null,
      "asignado_sid": null
    },
    "motivo": "mensaje_del_cliente",
    "fecha": "2026-10-25T02:14:20.390+02:00"
  }
}
conversacion.paso_a_persona — El agente IA pasa la conversación a una persona
{
  "id": "evt_01a1706884e77237b52b09c330d9c36a",
  "tipo": "conversacion.paso_a_persona",
  "creado": "2026-10-25T02:14:20.391+02:00",
  "cuenta": {
    "usuarios_sid": 12345
  },
  "numero": {
    "id": 1,
    "telefono": "+34 910 00 00 00",
    "phone_number_id": "106540352242922",
    "waba_id": "102290129340398"
  },
  "datos": {
    "conversacion": {
      "id": 1,
      "contacto": "34600000001",
      "estado": "abierta",
      "asignado_tipo": "usuario",
      "asignado_sid": null
    },
    "motivo": "pedido_por_el_agente",
    "fecha": "2026-10-25T02:14:20.391+02:00"
  }
}
conversacion.asignada — Una persona coge la conversación
{
  "id": "evt_01a1706884e97630ae08d9948833fbba",
  "tipo": "conversacion.asignada",
  "creado": "2026-10-25T02:14:20.393+02:00",
  "cuenta": {
    "usuarios_sid": 12345
  },
  "numero": {
    "id": 1,
    "telefono": "+34 910 00 00 00",
    "phone_number_id": "106540352242922",
    "waba_id": "102290129340398"
  },
  "datos": {
    "conversacion": {
      "id": 1,
      "contacto": "34600000001",
      "estado": "abierta",
      "asignado_tipo": "usuario",
      "asignado_sid": 4021
    },
    "motivo": "persona_coge",
    "fecha": "2026-10-25T02:14:20.393+02:00"
  }
}
conversacion.cerrada — Se cierra la conversación
{
  "id": "evt_01a1706884f270e8bf279abf4027c4b8",
  "tipo": "conversacion.cerrada",
  "creado": "2026-10-25T02:14:20.402+02:00",
  "cuenta": {
    "usuarios_sid": 12345
  },
  "numero": {
    "id": 1,
    "telefono": "+34 910 00 00 00",
    "phone_number_id": "106540352242922",
    "waba_id": "102290129340398"
  },
  "datos": {
    "conversacion": {
      "id": 1,
      "contacto": "34600000001",
      "estado": "cerrada",
      "asignado_tipo": "usuario",
      "asignado_sid": 4021
    },
    "motivo": "cerrada_por_persona",
    "fecha": "2026-10-25T02:14:20.402+02:00"
  }
}
contacto.baja — Un contacto pide la baja
{
  "id": "evt_01a1706884f7765287acb9a86057c25d",
  "tipo": "contacto.baja",
  "creado": "2026-10-25T02:14:20.407+02:00",
  "cuenta": {
    "usuarios_sid": 12345
  },
  "numero": {
    "id": 1,
    "telefono": "+34 910 00 00 00",
    "phone_number_id": "106540352242922",
    "waba_id": "102290129340398"
  },
  "datos": {
    "contacto": "34600000002",
    "fuente": "palabra",
    "palabra": "BAJA",
    "fecha": "2026-10-25T02:14:20.407+02:00"
  }
}
prueba — El botón «Probar» del panel
{
  "id": "evt_01a1706884fa72a4827ea0bf30fc1b0f",
  "tipo": "prueba",
  "creado": "2026-10-25T02:14:20.410+02:00",
  "cuenta": {
    "usuarios_sid": 12345
  },
  "numero": {
    "id": 1,
    "telefono": "+34 910 00 00 00",
    "phone_number_id": "106540352242922",
    "waba_id": "102290129340398"
  },
  "datos": {
    "mensaje": "Evento de prueba de MundoSMS"
  }
}

Tipos de mensaje

En mensaje.recibido y mensaje.enviado, datos.tipo dice qué es el mensaje y datos.texto trae su texto si lo tiene (el del mensaje, el pie de una foto, el título del botón pulsado…):

texto, imagen, audio, nota_voz, video, documento, sticker, ubicacion, contactos, reaccion, boton, respuesta_boton, respuesta_lista, respuesta_formulario, pedido, no_admitido (un tipo que WhatsApp no deja recibir), sistema y, en lo enviado, plantilla. Si WhatsApp añade un tipo que aún no conocemos, llega como desconocido en vez de perderse.

Si el mensaje viene de un anuncio de Facebook o Instagram («Click to WhatsApp»), lleva además anuncio con los datos del anuncio.

Fotos, audios, vídeos y documentos

El fichero no va dentro del evento. Lo que va es un adjunto con un enlace de descarga:

CampoQué es
urlEnlace para descargar el fichero, firmado y con fecha de caducidad (caduca): 30 días. No necesita usuario ni contraseña, así que trátalo como un dato privado.
mime, nombre, bytes, sha256Tipo, nombre del fichero (en documentos), tamaño y huella para comprobar la descarga.
guardadotrue si el fichero está disponible. Si es false, url va vacía y motivo dice por qué (más de 16 MB, WhatsApp no lo entregó, mensaje del historial importado…).

Si quieres conservar el fichero más de 30 días, descárgalo al recibir el evento. El evento de un mensaje con fichero sale cuando el fichero ya está guardado, normalmente en un par de segundos.

Preguntas frecuentes

¿Y si mi servidor está caído un rato?

No pierdes nada: reintentamos durante unas 22 horas y, pasado ese tiempo, lo tienes en el panel para reenviarlo. Si el servidor sigue caído un día entero, el destino se pausa solo y los eventos se guardan hasta que lo reactives.

¿Puedo usar el mismo destino para varios números?

Sí. Cada evento lleva numero, así que sabes siempre de cuál viene. También puedes poner un destino distinto a cada número.

¿Recibo lo que contesto desde la app del móvil?

Sí. Si usas tu número a la vez en la app WhatsApp Business y en MundoSMS, lo que escribes en el móvil llega como mensaje.enviado con origen: "app".

¿Cómo cambio el secreto?

En la ventana del destino, «Nuevo secreto». El anterior deja de valer al momento, así que actualízalo en tu servidor enseguida.

¿Puedo elegir qué eventos recibo?

Sí, con las casillas del destino. Un envío masivo genera varios mensaje.estado por destinatario (enviado, entregado, leído): si no los necesitas, desmárcalos.

¿Desde qué direcciones llegan?

Desde nuestros servidores en la Unión Europea. No filtres por dirección IP, porque puede cambiar: lo que garantiza que el evento es nuestro es la firma.

¿Dudas con la integración? Escríbenos y te ayudamos. Para enviar mensajes desde tu programa tienes nuestra API, y aquí tienes todo sobre la WhatsApp Business API de MundoSMS.


MundoSMS es proveedor tecnológico de la WhatsApp Business Platform de Meta. WhatsApp es una marca de Meta Platforms, Inc.; MundoSMS no está afiliado a Meta.