
Cómo Enviar Mensajes de WhatsApp desde TypeScript
whatsmeow-node te da métodos async tipados para cada tipo de mensaje de WhatsApp — texto, respuestas, menciones, multimedia, encuestas y reacciones. Esta guía cubre cada uno con ejemplos en TypeScript.
Requisitos Previos
- whatsmeow-node instalado (Guía de instalación)
- Una sesión vinculada (Cómo Vincular WhatsApp)
Configurar el Cliente
import { createClient } from "@whatsmeow-node/whatsmeow-node";
const client = createClient({ store: "session.db" });
async function main() {
const { jid: myJid } = await client.init();
if (!myJid) {
console.error("Not paired — run the pairing flow first");
process.exit(1);
}
await client.connect();
await client.waitForConnection();
const recipient = "5512345678@s.whatsapp.net";
// ... send messages here
}
main().catch(console.error);
Enviar un Mensaje de Texto
La forma más simple — pasa un string conversation:
const resp = await client.sendMessage(recipient, {
conversation: "Hello from TypeScript!",
});
console.log(`Sent with ID: ${resp.id}`);
sendMessage es el método de alto nivel. Recibe un JID y un objeto MessageContent, y devuelve un SendResponse con el id y timestamp del mensaje.
Responder con Cita
Para mostrar el mensaje original en una burbuja de cita, usa sendRawMessage con contextInfo:
// Assume we received a message and have its info
client.on("message", async ({ info, message }) => {
if (info.isFromMe) return;
const text =
(message.conversation as string) ??
(message.extendedTextMessage as { text?: string } | undefined)?.text;
if (!text) return;
await client.sendRawMessage(info.chat, {
extendedTextMessage: {
text: `You said: "${text}"`,
contextInfo: {
stanzaId: info.id,
participant: info.sender,
quotedMessage: { conversation: text },
},
},
});
});
@Mencionar Usuarios
Incluye los JID en mentionedJid y usa @<número> en el texto:
await client.sendRawMessage(groupJid, {
extendedTextMessage: {
text: `Hey @${memberJid.split("@")[0]}, check this out!`,
contextInfo: {
mentionedJid: [memberJid],
},
},
});
Para mencionar a todos en un grupo:
const group = await client.getGroupInfo(groupJid);
const jids = group.participants.map((p) => p.jid);
const mentions = jids.map((j) => `@${j.split("@")[0]}`).join(" ");
await client.sendRawMessage(groupJid, {
extendedTextMessage: {
text: `Attention: ${mentions}`,
contextInfo: { mentionedJid: jids },
},
});
Enviar Multimedia
Primero sube el archivo, luego envía los metadatos con un mensaje raw:
// Upload an image
const media = await client.uploadMedia("/path/to/photo.jpg", "image");
await client.sendRawMessage(recipient, {
imageMessage: {
URL: media.URL,
directPath: media.directPath,
mediaKey: media.mediaKey,
fileEncSHA256: media.fileEncSHA256,
fileSHA256: media.fileSHA256,
fileLength: String(media.fileLength),
mimetype: "image/jpeg",
caption: "Check this out!",
},
});
Otros tipos de multimedia siguen el mismo patrón — videoMessage, audioMessage, documentMessage, stickerMessage.
Los campos de respuesta de upload usan el casing exacto de protobuf: URL, fileSHA256, fileEncSHA256 — no url, fileSha256. Un casing incorrecto falla silenciosamente.
Enviar una Encuesta
const resp = await client.sendPollCreation(
groupJid,
"Where should we eat?", // question
["Pizza", "Sushi", "Tacos"], // options
1, // max selectable
);
Reaccionar a un Mensaje
// Add a reaction
await client.sendReaction(chat, senderJid, messageId, "🔥");
// Remove a reaction (empty string)
await client.sendReaction(chat, senderJid, messageId, "");
Editar un Mensaje Enviado
const sent = await client.sendMessage(recipient, {
conversation: "Hello!",
});
// Edit it — only works on your own messages
await client.editMessage(recipient, sent.id, {
conversation: "Hello! (edited)",
});
Eliminar un Mensaje
// Revoke for everyone — only your own messages, within the time limit
await client.revokeMessage(chat, myJid, sent.id);
Marcar como Leído
await client.markRead([info.id], info.chat, info.sender);
Ejemplo Completo
Un bot que repite los mensajes de texto como respuestas citadas:
import { createClient } from "@whatsmeow-node/whatsmeow-node";
import qrcode from "qrcode-terminal";
const client = createClient({ store: "session.db" });
client.on("error", (err) => console.error("Error:", err));
client.on("message", async ({ info, message }) => {
if (info.isFromMe) return;
const text =
(message.conversation as string) ??
(message.extendedTextMessage as { text?: string } | undefined)?.text;
if (!text) return;
await client.markRead([info.id], info.chat, info.sender);
await client.sendChatPresence(info.chat, "composing");
await client.sendRawMessage(info.chat, {
extendedTextMessage: {
text: `Echo: ${text}`,
contextInfo: {
stanzaId: info.id,
participant: info.sender,
quotedMessage: { conversation: text },
},
},
});
});
async function main() {
const { jid } = await client.init();
if (!jid) {
client.on("qr", ({ code }) => qrcode.generate(code, { small: true }));
await client.getQRChannel();
}
await client.connect();
console.log("Listening for messages...");
process.on("SIGINT", async () => {
await client.sendPresence("unavailable");
await client.disconnect();
client.close();
process.exit(0);
});
}
main().catch(console.error);
Errores Comunes
sendMessage vs sendRawMessagesendMessage es para texto simple. Para cualquier cosa estructurada (citas, menciones, multimedia), usa sendRawMessage con la forma completa del protobuf.
info.chatEn grupos, siempre envía a info.chat (el JID del grupo), no a info.sender (el individuo). Enviar a info.sender inicia una conversación privada.
WhatsApp limita la tasa de envío. Espacia los mensajes, especialmente en grupos. Consulta Límites de Tasa para más detalles.







