Como avisar seguidores da live por WhatsApp e SMS com Node.js

Capa do artigo Como avisar seguidores da live por WhatsApp e SMS com Node.js

Avisar seguidores da live por WhatsApp é a diferença entre começar a transmissão para três pessoas e começar com a sala cheia. O post no feed depende do algoritmo decidir mostrar; a mensagem no WhatsApp chega como notificação, com o link pronto para tocar, no momento em que a live está prestes a começar. Com SMS como reserva, nem quem está sem internet no app fica de fora.

A boa notícia para quem programa: a automação inteira cabe em um projeto Node.js pequeno. Um formulário de opt-in, uma tabela de seguidores, um script que envia pela API da APIBrasil (WhatsApp com fallback para SMS), um cron que dispara uma hora e dez minutos antes e um link rastreável para medir quem clicou.

O roteiro abaixo monta cada peça nessa ordem, com código para Node 18 ou superior, fetch nativo e SQLite para não depender de infraestrutura além do próprio processo.

Por que avisar seguidores da live por WhatsApp converte mais que o post

Um post ou story anunciando a live compete com todo o resto do feed e some em horas. A mensagem direta tem três vantagens práticas: chega como notificação no celular, carrega o link clicável e pode ser enviada no minuto certo, quando a pessoa consegue entrar na hora. Em vez de “lembra da live às 20h?”, o seguidor recebe “estamos ao vivo, entra aqui” com um toque de distância.

A contrapartida é responsabilidade: a lista só funciona enquanto for formada por quem pediu para estar nela, e a frequência precisa ser previsível. Duas mensagens por live, a primeira com antecedência e a segunda na largada, costumam ser o equilíbrio entre lembrar e incomodar. A estratégia de conteúdo por trás disso (o que dizer, como pedir o número, como manter o grupo aquecido) está bem detalhada em avisar o público da live pelo WhatsApp; aqui o foco é a automação que executa essa estratégia sem trabalho manual.

Captando o opt-in dos seguidores

Tudo começa com um formulário simples (nome e celular) em uma página sua, linkada na bio e mencionada durante as lives. Dois cuidados tornam a lista utilizável: normalizar o telefone para o formato internacional só com dígitos (5511999999999) e confirmar o número com uma mensagem de boas-vindas que pede uma resposta. Quem responde SIM entra; quem responde PARAR sai a qualquer momento. Esse duplo opt-in evita números digitados errado e deixa o consentimento registrado com data.

Se a sua live é de código, o formulário pode viver no README do projeto ou na página do canal; quem faz live de programação normalmente já tem público acostumado a seguir links do GitHub, e o aviso por WhatsApp funciona igual. O esquema de banco que sustenta tudo tem três tabelas:

// db.js
import Database from "better-sqlite3";

export const db = new Database("lives.db");
db.exec(`
  CREATE TABLE IF NOT EXISTS seguidores (
    id INTEGER PRIMARY KEY,
    nome TEXT NOT NULL,
    telefone TEXT UNIQUE NOT NULL,        -- 5511999999999
    canal TEXT NOT NULL DEFAULT 'whatsapp', -- whatsapp | sms
    confirmado INTEGER NOT NULL DEFAULT 0,  -- vira 1 quando responde SIM
    consentimento_em TEXT,
    token TEXT UNIQUE NOT NULL              -- usado no link rastreável
  );
  CREATE TABLE IF NOT EXISTS lives (
    id INTEGER PRIMARY KEY,
    titulo TEXT NOT NULL,
    url TEXT NOT NULL,
    inicia_em TEXT NOT NULL,                -- ISO 8601, ex.: 2026-10-20T20:00:00-03:00
    aviso_60 INTEGER NOT NULL DEFAULT 0,
    aviso_10 INTEGER NOT NULL DEFAULT 0
  );
  CREATE TABLE IF NOT EXISTS envios (
    id INTEGER PRIMARY KEY,
    live_id INTEGER NOT NULL,
    seguidor_id INTEGER NOT NULL,
    canal TEXT NOT NULL,
    minutos INTEGER NOT NULL,               -- 60 ou 10
    enviado_em TEXT NOT NULL,
    clicado_em TEXT
  );
`);

A rota de opt-in grava o seguidor e dispara a mensagem de confirmação. A resposta (SIM ou PARAR) chega pelo webhook de mensagens recebidas da API de WhatsApp, que atualiza a coluna confirmado:

// server.js (trecho)
import express from "express";
import crypto from "node:crypto";
import { db } from "./db.js";
import { enviarWhatsApp } from "./mensageria.js";

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.post("/opt-in", async (req, res) => {
  const telefone = String(req.body.telefone ?? "").replace(/\D/g, "");
  if (!/^55[1-9]{2}9?\d{8}$/.test(telefone)) return res.status(400).send("Telefone inválido");

  db.prepare(`
    INSERT INTO seguidores (nome, telefone, consentimento_em, token) VALUES (?, ?, ?, ?)
    ON CONFLICT(telefone) DO UPDATE SET nome = excluded.nome, consentimento_em = excluded.consentimento_em
  `).run(String(req.body.nome).trim(), telefone, new Date().toISOString(), crypto.randomUUID());

  await enviarWhatsApp(telefone, `Oi! Responda SIM para receber o aviso das lives. Para sair quando quiser, responda PARAR.`);
  res.send("Confira seu WhatsApp e responda SIM para confirmar.");
});

// Webhook de mensagens recebidas (wh_message do dispositivo na APIBrasil)
app.post(`/webhooks/whatsapp/${process.env.WEBHOOK_SECRET}`, (req, res) => {
  res.sendStatus(200); // responda rápido; processe depois
  const evento = req.body;
  if (evento?.wook !== "RECEIVE_MESSAGE") return;
  const texto = String(evento.content ?? "").trim().toUpperCase();
  const de = String(evento.from ?? "").replace(/\D/g, "");
  if (texto === "SIM") db.prepare("UPDATE seguidores SET confirmado = 1 WHERE telefone = ?").run(de);
  if (texto === "PARAR") db.prepare("UPDATE seguidores SET confirmado = 0 WHERE telefone = ?").run(de);
});

app.listen(3000);
Infográfico da automação de aviso de live com Node.js e APIBrasil: formulário de opt-in, lista no banco, cron 60 e 10 minutos antes, WhatsApp com fallback SMS e link rastreável

Script Node.js que dispara o aviso

O módulo de mensageria concentra as duas chamadas à APIBrasil. O WhatsApp usa POST /whatsapp/sendText com number e text, autenticado com o Bearer Token e o header DeviceToken do dispositivo conectado no painel. O SMS usa POST /sms/send/credits com number e message, só com o Bearer Token. Nas duas, a resposta pode vir com HTTP 200 e error igual a true, então a checagem é sempre no corpo. Os tokens ficam em variáveis de ambiente; o guia de variáveis de ambiente com .env mostra como organizar isso.

// mensageria.js
const BASE_URL = "https://gateway.apibrasil.io/api/v2";

async function post(caminho, corpo, headersExtras = {}) {
  const resposta = await fetch(BASE_URL + caminho, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.APIBRASIL_TOKEN}`,
      ...headersExtras,
    },
    body: JSON.stringify(corpo),
    signal: AbortSignal.timeout(15_000),
  });
  const json = await resposta.json().catch(() => ({}));
  if (!resposta.ok || json.error) {
    const erro = new Error(json.message || `HTTP ${resposta.status}`);
    erro.status = resposta.status;
    throw erro;
  }
  return json;
}

export const enviarWhatsApp = (number, text) =>
  post("/whatsapp/sendText", { number, text }, { DeviceToken: process.env.APIBRASIL_DEVICE_TOKEN });

export const enviarSms = (number, message) =>
  post("/sms/send/credits", { number, message });

/** WhatsApp primeiro; SMS se o seguidor preferir ou se o WhatsApp recusar o envio. */
export async function avisar(seguidor, texto) {
  if (seguidor.canal === "sms") {
    await enviarSms(seguidor.telefone, texto);
    return "sms";
  }
  try {
    await enviarWhatsApp(seguidor.telefone, texto);
    return "whatsapp";
  } catch (erro) {
    if (!erro.status || erro.status >= 500) throw erro; // instabilidade: deixe o chamador tentar depois
    await enviarSms(seguidor.telefone, texto);            // recusa definitiva (ex.: número sem WhatsApp)
    return "sms";
  }
}

A função avisar devolve o canal usado, que vai para a tabela envios. Esse registro é o que permite comparar depois quantos cliques vieram de cada canal. Se preferir uma versão mais completa do envio pelo WhatsApp, com mídia e fila, o guia de como enviar WhatsApp com Node.js aprofunda essa parte.

Agendando com cron: uma hora e dez minutos antes

Em vez de um cron por live, um único agendador roda a cada minuto, olha as lives futuras e decide se é hora do aviso de 60 ou de 10 minutos. As colunas aviso_60 e aviso_10 são marcadas antes do disparo, para que uma segunda execução no mesmo minuto não mande tudo de novo. A concorrência é limitada com p-limit, e o fuso é fixado em America/Sao_Paulo para que o horário da live seja interpretado como o público espera:

// agendador.js
import cron from "node-cron";
import pLimit from "p-limit";
import { db } from "./db.js";
import { avisar } from "./mensageria.js";

const limite = pLimit(3);

function montarTexto(seguidor, live, minutos, link) {
  const nome = seguidor.nome.split(" ")[0];
  return minutos === 60
    ? `${nome}, a live "${live.titulo}" começa em 1 hora. Link: ${link}`
    : `Estamos ao vivo em 10 minutos! Entra aqui: ${link}`;
}

async function dispararAviso(live, minutos) {
  const coluna = minutos === 60 ? "aviso_60" : "aviso_10";
  db.prepare(`UPDATE lives SET ${coluna} = 1 WHERE id = ?`).run(live.id); // marca antes para não duplicar

  const seguidores = db.prepare("SELECT * FROM seguidores WHERE confirmado = 1").all();
  const resultados = await Promise.allSettled(
    seguidores.map((s) =>
      limite(async () => {
        const link = `${process.env.APP_URL}/r/${live.id}/${s.token}`;
        const canal = await avisar(s, montarTexto(s, live, minutos, link));
        db.prepare("INSERT INTO envios (live_id, seguidor_id, canal, minutos, enviado_em) VALUES (?, ?, ?, ?, ?)")
          .run(live.id, s.id, canal, minutos, new Date().toISOString());
      })
    )
  );
  const falhas = resultados.filter((r) => r.status === "rejected").length;
  console.log(`live ${live.id} | aviso ${minutos} min | ${seguidores.length - falhas} enviados, ${falhas} falhas`);
}

cron.schedule("* * * * *", async () => {
  const agora = Date.now();
  const lives = db.prepare("SELECT * FROM lives WHERE inicia_em > ?").all(new Date(agora).toISOString());
  for (const live of lives) {
    const minutos = Math.round((new Date(live.inicia_em).getTime() - agora) / 60_000);
    if (minutos <= 60 && minutos > 10 && !live.aviso_60) await dispararAviso(live, 60);
    if (minutos <= 10 && !live.aviso_10) await dispararAviso(live, 10);
  }
}, { timezone: "America/Sao_Paulo" });

Cadastrar uma live é um INSERT na tabela lives com título, URL e horário em ISO 8601 com fuso. Se a live atrasar, basta atualizar inicia_em: o agendador recalcula a diferença no próximo minuto, e o aviso de 10 minutos sai no novo horário. Para listas grandes, troque o p-limit por uma fila persistente (BullMQ com Redis), mantendo o mesmo módulo de mensageria.

Link da live e lembrete por SMS

O link enviado não é o da plataforma, e sim um redirecionamento no seu domínio, com o id da live e o token do seguidor. Ele continua curto, cabe no SMS e registra o clique antes de mandar a pessoa para o YouTube, o Instagram ou a Twitch. Se você ainda está montando a transmissão, o passo a passo de como fazer live no YouTube explica onde pegar a URL permanente do canal ao vivo, que é a mais prática de cadastrar com antecedência.

// server.js (trecho): redirecionamento rastreável
app.get("/r/:liveId/:token", (req, res) => {
  const live = db.prepare("SELECT * FROM lives WHERE id = ?").get(req.params.liveId);
  if (!live) return res.sendStatus(404);

  const seguidor = db.prepare("SELECT id FROM seguidores WHERE token = ?").get(req.params.token);
  if (seguidor) {
    db.prepare(`
      UPDATE envios SET clicado_em = COALESCE(clicado_em, ?)
      WHERE live_id = ? AND seguidor_id = ?
    `).run(new Date().toISOString(), live.id, seguidor.id);
  }

  const destino = new URL(live.url);
  destino.searchParams.set("utm_source", "aviso-live");
  destino.searchParams.set("utm_campaign", `live-${live.id}`);
  res.redirect(302, destino.toString());
});

Avisar seguidores da live por WhatsApp cobre a maior parte da lista; o SMS entra em dois momentos. Como fallback automático, quando o WhatsApp recusa o número; e como canal preferido para quem marcou essa opção no formulário. O texto do SMS precisa caber em 160 caracteres com o link incluído, então mantenha o domínio do redirecionamento curto e evite acentos e emojis, que reduzem o limite por mensagem. Teste o texto final no seu próprio número antes da primeira live.

Medindo cliques e ajustando

Com envios e cliques na mesma tabela, as perguntas importantes viram uma consulta: quantos avisos saíram por canal, quantos foram clicados e qual dos dois horários trouxe mais gente.

SELECT canal, minutos,
       COUNT(*)                                   AS enviados,
       SUM(clicado_em IS NOT NULL)                AS cliques,
       ROUND(100.0 * SUM(clicado_em IS NOT NULL) / COUNT(*), 1) AS ctr
FROM envios
WHERE live_id = ?
GROUP BY canal, minutos;

Alguns ajustes que esses números costumam sugerir: se o aviso de 60 minutos tem poucos cliques, ele serve mais como lembrete do que como chamada, e o texto pode ser mais curto; se o de 10 minutos concentra os cliques, vale testar 5 minutos ou o exato momento em que a live abre. Seguidores que nunca clicam em várias lives seguidas podem receber uma pergunta antes de continuar recebendo, o que mantém a lista saudável. Cruze também com o pico de espectadores da plataforma para ver se o aviso está chegando antes ou depois do horário real de entrada.

Perguntas frequentes

Posso avisar quem só me segue no Instagram ou no YouTube?

Não sem o número e o consentimento da pessoa. As plataformas não entregam o telefone dos seguidores, e disparar para listas compradas é a forma mais rápida de ter o número bloqueado. O caminho é o formulário de opt-in divulgado na bio, nos stories e durante as lives.

WhatsApp ou SMS: qual usar?

WhatsApp como canal principal, pelo link clicável e pela conversa que pode continuar depois. SMS como reserva para números sem WhatsApp e para quem prefere; ele chega em qualquer celular, sem internet. O código acima trata os dois com a mesma função, então a escolha vira uma coluna no cadastro.

Quantas mensagens enviar por live?

Duas resolvem a maioria dos casos: uma com cerca de uma hora de antecedência e outra na largada. Para eventos grandes, um aviso no dia anterior pode fazer sentido, desde que o seguidor saiba disso ao se cadastrar. Mais do que isso tende a gerar descadastro.

Preciso da API oficial do WhatsApp para isso?

A APIBrasil oferece tanto planos baseados em dispositivo conectado quanto a API oficial; em ambos, a chamada usa Bearer Token e DeviceToken, e o endpoint de texto é o mesmo. A escolha depende do volume e das exigências do seu caso; consulte o catálogo na documentação para comparar.

E se a live atrasar ou for cancelada?

Atrasou: atualize inicia_em e o agendador recalcula sozinho; se o aviso de 60 minutos já saiu, só o de 10 será reenviado no novo horário. Cancelou: apague a live ou marque as duas colunas de aviso como enviadas e, se quiser, dispare uma mensagem curta avisando a nova data.

Próximos passos

Com formulário, agendador e redirecionamento no ar, avisar seguidores da live por WhatsApp deixa de ser tarefa manual: o sistema cuida da divulgação enquanto você prepara a transmissão. Comece com uma lista pequena, confira o texto nos dois canais e acompanhe o CTR das primeiras lives antes de ampliar. Para rodar, gere o token no painel da APIBrasil, conecte o dispositivo de WhatsApp e envie o primeiro aviso para o seu próprio número.

Loading

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *