Un agente que contesta WhatsApp
De un mensaje que entra por WhatsApp a una respuesta del Met, con tu lógica en el medio y el traspaso a una persona cuando hace falta.
Con el autopiloto encendido, Meteor ya contesta los mensajes de WhatsApp solo. Esta receta es para lo otro: cuando quieres tu propia lógica en el medio — consultar tu inventario antes de responder, escalar según el cliente, escribir en tu CRM, decidir cuándo calla el Met y contesta una persona.
Antes de empezar
Conecta el canal de WhatsApp a tu workspace desde el panel (Canales → WhatsApp). La API no conecta canales: eso pasa una vez y necesita la aprobación de Meta.
Tu key necesita cinco scopes, uno por cada cosa que hace la receta:
| scope | para qué |
|---|---|
webhooks:manage | registrar el endpoint que recibe los eventos |
conversations:read | recibir contact.message.received |
runs:execute | ejecutar el Met |
channels:send | responderle al contacto |
handoff:manage | apagar y prender el autopiloto |
1. Suscríbete a los mensajes que entran
import Met from '@meteor.ia/sdk';
const met = new Met(process.env.MET_API_KEY!, {
workspaceId: Number(process.env.MET_WORKSPACE_ID),
});
const sub = await met.webhooks.subscriptions.create({
url: 'https://tu-servidor.com/webhooks/met',
enabled_events: ['contact.message.received'],
});
console.log(sub.secret); // whsec_… — se muestra UNA sola vez
Guarda el secret apenas lo recibes: lo necesitas para verificar cada entrega y no se vuelve a mostrar.
2. Recibe el mensaje y contesta
El evento llega con este cuerpo:
{
"contact_id": 4821,
"message_id": 99312,
"channel_id": 12,
"channel_type": "whatsapp",
"content": "¿Todavía tienen la bici azul?",
"attachments": []
}import express from 'express';
const app = express();
// El cuerpo CRUDO es obligatorio: si tu framework re-serializa el JSON antes de
// verificar, la firma deja de coincidir aunque el contenido sea idéntico.
app.use('/webhooks/met', express.raw({ type: 'application/json' }));
app.post('/webhooks/met', async (req, res) => {
let evento;
try {
evento = met.webhooks.constructEvent(
req.body,
req.headers['x-met-signature'] as string,
process.env.MET_WEBHOOK_SECRET!,
);
} catch {
return res.status(400).send('firma inválida');
}
// Responde YA. Meteor reintenta si no ve un 2xx, y un Met puede tardar varios
// segundos: contestar después de pensar te duplica los mensajes.
res.sendStatus(200);
if (evento.type !== 'contact.message.received') return;
const { contact_id, content } = evento.data;
const run = await met.runs.create({ input: content });
await met.contacts.sendMessage(contact_id, run.output);
});
app.listen(3000);El run que creas por API no queda atado al contacto: recibe el texto que le pasas y nada más. Si quieres que el Met responda sabiendo con quién habla, arma tú el input — por ejemplo con met.contacts.retrieve(contact_id) y met.contacts.messages(contact_id) — o usa conversation_id para que la conversación tenga memoria entre runs.
3. Pásasela a una persona cuando haga falta
Un Met que no sabe algo tiene que poder soltar la conversación. Con el autopiloto apagado los mensajes siguen llegando a tu bandeja, pero el Met deja de responder a ese contacto hasta que lo vuelvas a prender.
if (/hablar con (alguien|una persona|un asesor)/i.test(content)) {
await met.contacts.setAutopilot(contact_id, false);
await met.contacts.sendMessage(contact_id, 'Te comunico con alguien del equipo 👋');
await met.contacts.createNote(contact_id, 'Pidió atención humana.');
return;
}
Para devolverle el control al Met: met.contacts.setAutopilot(contact_id, true).
La ventana de 24 horas
Esto no es un límite de Meteor y no se puede saltar: WhatsApp solo te deja escribir libremente durante las 24 horas siguientes al último mensaje de la persona. Pasada esa ventana, lo único que entra es una plantilla aprobada por Meta:
await met.contacts.sendTemplate(contact_id, {
name: 'recordatorio_cita',
language: 'es',
namespace: process.env.WA_TEMPLATE_NAMESPACE!, // obligatorio
params: { '1': 'Ana', '2': 'martes a las 3' },
});
Si tu flujo contesta a destiempo —una cola, un reintento nocturno— esa ventana es la primera causa a mirar, antes que la key o los scopes.
Probarlo sin desplegar nada
El CLI te reenvía los eventos reales del workspace a tu máquina, así que puedes escribir el handler contra tráfico de verdad antes de tener servidor:
met listen --forward http://localhost:3000/webhooks/met --types contact.message.received
Y después
- El esquema de cada evento, los reintentos y el log de entregas: Webhooks.
- Contestar en vivo, palabra por palabra, en vez de esperar el run completo: Mostrar un run en vivo en tu UI.
- Que el Met consulte tus propios datos al responder: Datos: colecciones e ítems.