Pular para o conteúdo

Turbo Notify no Supabase

Quando uma linha muda no seu projeto Supabase, o seu cliente recebe uma mensagem de WhatsApp. Um Database Webhook chama uma Edge Function, e a função envia a mensagem pela API do Turbo Notify. A função é pequena, não tem dependências e vem com os seus testes, no repositório de exemplos (abre em nova aba).

  • Uma conta do Turbo Notify com um número conectado (começo rápido).
  • Uma chave de API só com a permissão de enviar mensagens (messages:send), criada em API Keys (abre em nova aba).
  • Um projeto Supabase e a CLI do Supabase, autenticada e ligada ao projeto.
  • Uma tabela com o telefone do cliente em formato internacional (+ e o código do país, sem espaços) e uma coluna que registra se a pessoa concordou em receber mensagens de WhatsApp. O exemplo usa phone e whatsapp_opt_in.
  1. Copie a pasta da função supabase/functions/notify-whatsapp do exemplo para o seu projeto e acrescente este bloco ao seu supabase/config.toml:

    supabase/config.toml
    [functions.notify-whatsapp]
    verify_jwt = false

    O banco chama a função sem um token de usuário, então a verificação de token da plataforma fica desligada para esta função. A função confere um segredo próprio (próximo passo).

  2. Crie o segredo que o banco vai enviar e guarde-o, junto com a sua chave, como segredos da função:

    openssl rand -hex 32
    supabase secrets set TN_API_KEY=SUA_CHAVE WEBHOOK_SECRET=O_VALOR_ACIMA
  3. Publique (o CLI empacota a função num contêiner, então o Docker precisa estar rodando):

    supabase functions deploy notify-whatsapp
  4. No dashboard do Supabase, abra Integrations, depois Database Webhooks, e crie um webhook:

    • Tabela: a sua (o exemplo usa orders), com os eventos Insert e Update.
    • Tipo: Supabase Edge Functions, função notify-whatsapp, método POST.
    • Cabeçalho HTTP: x-webhook-secret com o valor que você gerou.

    Criar pelo dashboard mantém o segredo fora do seu repositório, e o dashboard ativa a extensão pg_net que os webhooks usam. O exemplo traz o mesmo webhook em SQL em supabase/webhook.sql, com marcadores no lugar dos valores. Num projeto hospedado, ative os Database Webhooks uma vez no dashboard antes de aplicá-lo: o SQL usa o schema supabase_functions, e um projeto que nunca ativou webhooks não o tem, então o arquivo falha com schema "supabase_functions" does not exist e não muda nada. A primeira instrução dele ativa o pg_net; sem essa extensão, todo insert e update na tabela falha.

  5. Mude o que é enviado em message.ts: ele decide quais eventos geram mensagem e o que o texto diz.

O Turbo Notify só precisa da chave de API, e ela fica como segredo do Supabase: nunca numa tabela, nunca no repositório. Crie a chave só com messages:send. Se ela vazar, o máximo que consegue é enviar mensagens, e você a revoga em API Keys (abre em nova aba) sem mexer em mais nada.

A função tem ainda uma segunda proteção do lado do Supabase: só aceita uma chamada que traga o x-webhook-secret que você definiu. Ninguém consegue disparar uma mensagem chamando o endereço da função.

Primeiro um teste que não envia nada. Chame a função com uma linha que não concordou em receber mensagens (troque o endereço e o segredo):

curl -X POST https://SEU_PROJETO.supabase.co/functions/v1/notify-whatsapp \
-H "x-webhook-secret: O_VALOR_ACIMA" \
-d '{"type":"INSERT","table":"orders","schema":"public","record":{"id":1,"phone":"+12025550123","whatsapp_opt_in":false},"old_record":null}'

A resposta é {"skipped":"no_opt_in"}. Com um segredo errado, a resposta é 401.

Depois o teste de verdade: insira uma linha na sua tabela com o seu próprio número e a coluna de consentimento verdadeira. Em alguns segundos a mensagem chega no seu celular. A função responde ao banco na hora; se a mensagem foi entregue chega depois, por webhook ou polling.

Para testar o caminho inteiro sem um projeto hospedado, rode no Supabase local. O README do exemplo (abre em nova aba) traz os comandos. Rodamos exatamente esse caminho: uma linha com consentimento gerou a mensagem no celular, o mesmo evento enviado duas vezes gerou uma só mensagem, e uma linha sem consentimento não enviou nada.

Mude a função e rode supabase functions deploy notify-whatsapp de novo. O endereço do Turbo Notify nunca muda.

  • O consentimento é seu. Envie só para quem concordou em receber mensagens de WhatsApp de você. O exemplo recusa uma linha em que a coluna de consentimento não seja verdadeira: sem o registro, a mensagem não sai.
  • Um número conectado como o WhatsApp Web. O Turbo Notify conecta o seu número como um aparelho vinculado, sem cadastro na Meta e sem aprovação de modelo. É um serviço independente, sem afiliação com a Meta, e cada envio segue um ritmo que ajuda a proteger o número. Quem decide sobre restrições é o WhatsApp, e um número pode ser restringido qualquer que seja o ritmo. As práticas que o mantêm saudável estão em Proteja o seu número.
  • A cota e o ritmo do seu plano valem para cada mensagem que a função envia, como para qualquer outro envio. Veja Limites.
  • Um evento, uma mensagem. A função envia com cada mensagem uma chave que identifica o evento, então o mesmo evento entregue duas vezes é enviado uma só.
  • Falhas. A função responde 502 quando tentar de novo pode ajudar (um limite de taxa, número desconectado, uma indisponibilidade do Turbo Notify) e 200 com o motivo quando não pode (um pedido inválido). Quando o problema é a conta (a assinatura não está ativa, ou a cota de mensagens ou de contatos do plano acabou até o ciclo virar) ou a chave está errada, responde 500 e o motivo aparece nos logs da função, para que as notificações não sejam descartadas em silêncio.
  • Projetos hospedados. Rodamos esse caminho no Supabase local e num projeto hospedado novo: a tabela, os segredos e a publicação, uma linha com consentimento gerando a mensagem, a mudança de status gerando a segunda, e o mesmo evento enviado de novo devolvendo a mesma mensagem. No projeto hospedado o banco chamou a função direto pelo pg_net. Não rodamos lá o supabase/webhook.sql em si, nem o formulário do webhook no dashboard. A função não depende de o Supabase chamá-la de novo depois de uma resposta com falha: o mesmo evento é enviado uma só vez de qualquer forma.