Enviar WhatsApp desde Google Sheets con Apps Script (paso a paso)
Para enviar WhatsApp desde Google Sheets, abre Extensiones > Apps Script, guarda la clave y el secreto de la API en Propiedades del script y usa UrlFetchApp.fetch para llamar a POST /api/v1/{instanceId}/send/text en cada fila. El script de abajo lee Nombre, Teléfono y Mensaje, envía, escribe OK o el error en la columna Estado y salta a quien ya fue. También mostramos el menú dentro de la hoja y cómo recibir respuestas por webhook.
Publicado el · 6 min de lectura
Este artículo también está disponible en: Português · English
Qué necesitas y cómo organizar la hoja
Una cuenta en ZapZap, una instancia conectada por código QR, la clave y el secreto de la API (x-api-key y x-api-secret) y el ID de la instancia. La hoja necesita cuatro columnas en la primera pestaña, en este orden: A Nombre, B Teléfono, C Mensaje, D Estado.
El teléfono va con código de país y solo dígitos, por ejemplo 5511999999999. Si la columna está formateada como número, Sheets puede convertir el valor a notación científica o quitar ceros; formatea la columna B como Texto sin formato. La columna D empieza vacía: el script escribe ahí el resultado de cada envío, y eso es lo que impide el reenvío doble. El resumen del paso a paso también está en la integración con Google Sheets.
Cómo guardar la clave y el secreto sin escribirlos en el código
Nunca pegues credenciales en el código de la hoja: quien tenga acceso de edición lo ve todo. En el editor de Apps Script, abre Configuración del proyecto (el engranaje) y, en Propiedades del script, agrega ZAPZAP_KEY, ZAPZAP_SECRET y ZAPZAP_INSTANCE. El código lee esos valores con PropertiesService.
Ojo con quién tiene acceso: el dueño del script y quien tenga permiso de edición en la hoja puede abrir el editor y leer las propiedades. Comparte la hoja como Lector con quien solo necesita ver los datos, y usa una instancia separada para el uso en hoja de cálculo, no el número principal del negocio.
Cómo el script lee la hoja y llama a la API
El código de abajo recorre las filas desde la segunda, salta las que ya tienen Estado, envía el mensaje con UrlFetchApp.fetch y escribe el resultado en la columna D. El parámetro muteHttpExceptions evita que un error HTTP interrumpa el script: lees el código de respuesta y decides qué escribir.
Utilities.sleep entre envíos crea un intervalo. El valor es decisión tuya: no existe un número oficial de seguridad de WhatsApp, y una ráfaga idéntica a una lista fría es el comportamiento que más pesa. Mira la guía de antibloqueo antes de subir el volumen.
Apps Script
function enviarMensagens() {
const props = PropertiesService.getScriptProperties();
const key = props.getProperty("ZAPZAP_KEY");
const secret = props.getProperty("ZAPZAP_SECRET");
const instancia = props.getProperty("ZAPZAP_INSTANCE");
const url = "https://api.zapzapapi.com/api/v1/" + instancia + "/send/text";
const aba = SpreadsheetApp.getActiveSheet();
const linhas = aba.getDataRange().getValues();
for (let i = 1; i < linhas.length; i++) {
const [nome, telefone, mensagem, status] = linhas[i];
if (status || !telefone || !mensagem) continue;
const texto = String(mensagem).replace("{nome}", nome);
const resposta = UrlFetchApp.fetch(url, {
method: "post",
contentType: "application/json",
headers: { "x-api-key": key, "x-api-secret": secret },
payload: JSON.stringify({ number: String(telefone), text: texto }),
muteHttpExceptions: true,
});
const codigo = resposta.getResponseCode();
aba.getRange(i + 1, 4).setValue(
codigo < 300 ? "OK " + new Date().toLocaleString("pt-BR") : "ERROR " + codigo + ": " + resposta.getContentText().slice(0, 120)
);
SpreadsheetApp.flush();
Utilities.sleep(3000 + Math.floor(Math.random() * 4000));
}
}Cómo crear un menú dentro de la hoja para enviar con un clic
Con la función onOpen, Sheets gana un menú WhatsApp al abrir el archivo. Quien opera la hoja no necesita entrar al editor de código. En la primera ejecución Google pide autorización para que el script acceda a la hoja y haga peticiones externas; es lo esperado.
Agrega también una función de prueba que envíe solo a tu número, para validar credenciales e instancia antes de ejecutar la lista completa.
Apps Script
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("WhatsApp")
.addItem("Enviar pendentes", "enviarMensagens")
.addItem("Enviar teste para mim", "enviarTeste")
.addToUi();
}
function enviarTeste() {
const props = PropertiesService.getScriptProperties();
const url = "https://api.zapzapapi.com/api/v1/" + props.getProperty("ZAPZAP_INSTANCE") + "/send/text";
const r = UrlFetchApp.fetch(url, {
method: "post",
contentType: "application/json",
headers: { "x-api-key": props.getProperty("ZAPZAP_KEY"), "x-api-secret": props.getProperty("ZAPZAP_SECRET") },
payload: JSON.stringify({ number: props.getProperty("MEU_NUMERO"), text: "Test from Google Sheets" }),
muteHttpExceptions: true,
});
SpreadsheetApp.getUi().alert("Response " + r.getResponseCode() + ": " + r.getContentText());
}Cómo enviar PDF o imagen desde una columna de la hoja
Agrega una columna E con la URL pública del archivo y usa /send/media con type, file, text (leyenda) y docName. El archivo debe ser accesible por URL pública; un enlace de Drive solo funciona si está abierto a cualquiera con el enlace y apunta al archivo en sí, no a la página de vista previa.
Para listas mayores, considera la cola de envío de la propia API (POST /sender/simple), que recibe un arreglo de numbers, el tipo, el texto y un intervalo mínimo y máximo entre mensajes (delayMin y delayMax) y programa el envío en el servidor. Así tu script no tiene que quedarse ejecutando, lo que también evita el límite de tiempo de ejecución de Apps Script (consulta las cuotas vigentes en la documentación de Google).
Apps Script
function enviarDocumento(numero, urlArquivo, nome, legenda) {
const props = PropertiesService.getScriptProperties();
const url = "https://api.zapzapapi.com/api/v1/" + props.getProperty("ZAPZAP_INSTANCE") + "/send/media";
return UrlFetchApp.fetch(url, {
method: "post",
contentType: "application/json",
headers: { "x-api-key": props.getProperty("ZAPZAP_KEY"), "x-api-secret": props.getProperty("ZAPZAP_SECRET") },
payload: JSON.stringify({ number: numero, type: "document", file: urlArquivo, docName: nome, text: legenda }),
muteHttpExceptions: true,
});
}Cómo recibir las respuestas de los clientes en la propia hoja
Publica el script como aplicación web (Implementar > Nueva implementación > Aplicación web) y usa la URL generada como webhook de la instancia. La función doPost recibe el JSON del evento y escribe una fila en una pestaña Respuestas. Configura el webhook con POST a /webhook, enviando url, enabled true y events con messages.
Dos cuidados. Primero, comprueba la estructura exacta del evento en un log real: guarda el cuerpo crudo en la primera versión y solo después mapea los campos. Segundo, Apps Script como aplicación web tiene límites de tiempo y de ejecuciones simultáneas; sirve bien para volumen bajo o medio y no sustituye a un servidor en una operación grande. La guía de webhook explica el diseño para volumen mayor.
Apps Script
function doPost(e) {
const evento = JSON.parse(e.postData.contents);
const aba = SpreadsheetApp.getActive().getSheetByName("Respostas") || SpreadsheetApp.getActive().insertSheet("Respostas");
aba.appendRow([new Date(), JSON.stringify(evento).slice(0, 4000)]);
return ContentService.createTextOutput("ok");
}cURL (registrar o webhook)
curl -X POST https://api.zapzapapi.com/api/v1/SEU_INSTANCE_ID/webhook \
-H "Content-Type: application/json" \
-H "x-api-key: SUA_API_KEY" \
-H "x-api-secret: SEU_API_SECRET" \
-d '{"url":"https://script.google.com/macros/s/SEU_ID/exec","enabled":true,"events":["messages"]}'Qué errores aparecen y qué hacer
| Síntoma | Causa probable | Solución |
|---|---|---|
| ERROR 401 o 403 en la columna Estado | Propiedades del script incorrectas o con espacio | Reabrir las Propiedades y revisar los valores |
| Error 400 | Teléfono sin código de país, con símbolos o vacío | Formatear la columna B como texto y limpiar el número |
| Excepción de autorización | Primera ejecución sin permiso | Autorizar el script cuando Google lo pida |
| El script se detiene a la mitad | Límite de tiempo de ejecución | Enviar por lotes o usar la cola POST /sender/simple |
| No llega nada a WhatsApp, estado OK | Instancia desconectada | Ver GET /instance/status y reconectar por código QR |
| El webhook no registra | Implementación antigua o sin acceso para cualquiera | Crear una nueva implementación como Aplicación web con acceso para cualquiera |
Antes de cualquier investigación, ejecuta la prueba del menú. Si responde 200, credenciales e instancia están correctas, y el problema está en los datos de la hoja.
Buenas prácticas para usar una hoja como panel de envío
Una hoja es excelente para empezar y para operaciones pequeñas. Registra siempre el resultado por fila (para auditoría y para no reenviar), usa un número dedicado a la hoja, escribe mensajes con variación en vez de texto idéntico para toda la lista y da una salida fácil a quien no quiere recibir. Escribirle a quien no pidió contacto es un problema en cualquier herramienta, y la protección de datos aplica igual.
Cuando la lista o la lógica crezca, lleva la misma idea a n8n o a código, manteniendo la misma API. La referencia de send/text y el inicio rápido ayudan a dar el siguiente paso.
Resumen rápido
- En Apps Script, UrlFetchApp.fetch llama a POST /send/text con x-api-key y x-api-secret en headers y JSON en payload.
- Guarda la clave y el secreto en Propiedades del script, nunca en el código de la hoja.
- Escribe el resultado en la columna Estado y salta las filas ya enviadas para no duplicar.
- Para listas grandes, usa la cola POST /sender/simple en vez de mantener el script ejecutándose.
- Recibe respuestas publicando el script como Aplicación web y registrando su URL en POST /webhook.
Sigue leyendo
Lee también
Preguntas frecuentes
¿Tengo que pagarle algo a Google para usar Apps Script?
Apps Script es un recurso de Google Workspace y de las cuentas personales, con cuotas de uso que varían según el tipo de cuenta. Consulta las cuotas vigentes en la documentación de Google antes de planear un volumen alto.
¿Cómo envío a una lista grande sin que el script se pase de tiempo?
Usa la cola de envío de la API (POST /sender/simple) con los números en un arreglo y un intervalo mínimo y máximo entre mensajes. El servidor programa y envía, y el script solo hace una llamada.
El teléfono aparece en notación científica en la hoja. ¿Y ahora?
Formatea la columna del teléfono como Texto sin formato antes de pegar los números y mantén código de país y de área, solo dígitos. En el script, convierte con String(telefono).
¿Puedo recibir la respuesta del cliente en la hoja?
Sí. Publica el script como Aplicación web con una función doPost, registra la URL en POST /webhook con el evento messages y guarda el cuerpo recibido en una pestaña. Comprueba el formato del evento en un log real antes de mapear las columnas.
¿Es seguro dejar la clave de la API en la hoja?
Guárdala en Propiedades del script, no en celdas ni en el código. Aun así, quien tiene acceso de edición puede leerlas. Limita quién edita, usa una instancia dedicada y genera credenciales nuevas si alguien sale del proyecto.
Convierte tu hoja de cálculo en un canal de WhatsApp
Crea la cuenta, verifica tu teléfono y recibe el crédito de bienvenida, sin depositar (uno por teléfono, sujeto a la campaña vigente). Conecta por código QR y pega el script.
