Integração

API de WhatsApp para Supabase

Dispare WhatsApp a partir de uma Edge Function do Supabase, reagindo a mudanças no banco.

// supabase/functions/avisar-whatsapp/index.ts
Deno.serve(async (req) => {
  // payload de um Database Webhook do Supabase
  const { record } = await req.json();

  const res = await fetch("https://zapixo.com.br/api/v1/messages/text", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${Deno.env.get("ZAPIXO_API_KEY")}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      instance: "app",
      number: record.telefone,
      text: `Olá ${record.nome}, seu cadastro foi confirmado!`,
    }),
  });

  if (!res.ok) {
    console.error("zapixo:", res.status, await res.text());
    return new Response("erro ao enviar", { status: 500 });
  }

  const { messageId } = await res.json();
  return Response.json({ messageId });
});

// 1. supabase secrets set ZAPIXO_API_KEY=zpx_...
// 2. supabase functions deploy avisar-whatsapp
// 3. crie um Database Webhook na tabela, apontando para a função

No Supabase, o caminho natural é uma Edge Function: ela guarda a chave do Zapixo como secret, envia pela API e também recebe o webhook das mensagens para gravar no Postgres.

Para disparar a partir do banco — cadastro novo, pedido pago —, um Database Webhook chama a função a cada insert ou update. A função traduz o evento do banco para a chamada do Zapixo: os formatos são diferentes, e é nela que a chave e o tratamento de erro ficam.

O que dá para fazer com Supabase e WhatsApp

  • Confirmação de cadastro

    Linha nova na tabela de usuários dispara a mensagem de boas-vindas.

  • Aviso de mudança de status

    Pedido que passa para pago ou enviado vira mensagem para o cliente, uma vez só.

  • Caixa de entrada no próprio app

    As mensagens recebidas vão para uma tabela, e o Realtime do Supabase mostra cada uma no app assim que chega.

  • Bot com dado do banco

    A função consulta o Postgres e responde com o dado real: pedido, agenda, saldo.

Enviar mensagens pelo Supabase

  1. Guarde a chave como secret

    O secret fica disponível só nas Edge Functions, por Deno.env.get. Nunca coloque a chave no app cliente: com a anon key pública, qualquer usuário a leria.

    supabase secrets set ZAPIXO_API_KEY=zpx_...
  2. Uma função de envio compartilhada

    Pasta que começa com _ não vira função: é o lugar do código comum entre elas.

    // supabase/functions/_shared/zapixo.ts
    export async function enviarWhatsApp(numero: string, texto: string) {
      const res = await fetch("https://zapixo.com.br/api/v1/messages/text", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${Deno.env.get("ZAPIXO_API_KEY")}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ instance: "app", number: numero.replace(/\D/g, ""), text: texto }),
      });
    
      if (!res.ok) throw new Error(`Zapixo ${res.status}: ${await res.text()}`);
      return res.json() as Promise<{ success: true; messageId?: string }>;
    }
  3. Dispare pelo Database Webhook

    Em Database › Webhooks, crie o gatilho na tabela, do tipo Supabase Edge Functions, apontando para a função. Ela recebe type, table, record e old_record — compare os dois para avisar só quando o status mudar de fato:

    import { enviarWhatsApp } from "../_shared/zapixo.ts";
    
    Deno.serve(async (req) => {
      const { type, record, old_record } = await req.json();
    
      if (type === "UPDATE" && record.status === "pago" && old_record?.status !== "pago") {
        await enviarWhatsApp(record.telefone, `Pagamento do pedido ${record.id} confirmado.`);
      }
    
      return new Response("ok");
    });

Receber mensagens no Supabase

  1. Publique a função sem verificação de JWT

    Por padrão, toda Edge Function exige um JWT do Supabase no header. O Zapixo não manda esse header, então a função que recebe o webhook é publicada sem essa verificação — e passa a se proteger pelo segredo na URL:

    supabase secrets set ZAPIXO_WEBHOOK_SECRET=um-segredo-longo
    supabase functions deploy receber-whatsapp --no-verify-jwt
  2. Cole a URL no painel

    Em Webhook de mensagens, com o segredo no fim: https://SEU-PROJETO.supabase.co/functions/v1/receber-whatsapp?chave=SEU_SEGREDO.

  3. Grave sem duplicar

    Com message_id como coluna única, a repetição do webhook vira nada. SUPABASE_URL e SUPABASE_SERVICE_ROLE_KEY já existem nas Edge Functions, sem configurar:

    import { createClient } from "npm:@supabase/supabase-js@2";
    
    const supabase = createClient(
      Deno.env.get("SUPABASE_URL")!,
      Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
    );
    
    Deno.serve(async (req) => {
      const segredo = Deno.env.get("ZAPIXO_WEBHOOK_SECRET");
      if (!segredo || new URL(req.url).searchParams.get("chave") !== segredo) {
        return new Response(null, { status: 401 });
      }
    
      const { event, data } = await req.json();
      if (event === "messages.upsert" && !data?.key?.fromMe) {
        await supabase.from("mensagens_whatsapp").upsert(
          {
            message_id: data.key.id,
            numero: data.key.remoteJid.split("@")[0],
            texto: data.message?.conversation ?? data.message?.extendedTextMessage?.text ?? "",
          },
          { onConflict: "message_id", ignoreDuplicates: true },
        );
      }
    
      return new Response(null, { status: 200 });
    });

Erros comuns e como resolver

401 em toda chamada do Zapixo
A função foi publicada com a verificação de JWT ligada. Publique de novo com --no-verify-jwt — e confira que ela confere o segredo.
Função pública sem segredo
Sem JWT, qualquer um que souber a URL chama a função. A conferência do segredo no começo do código não é opcional.
Service role no lugar errado
A service role ignora o RLS. Ela fica só dentro da função: nunca no app, nunca em log, nunca devolvida na resposta.
Aviso disparado em todo update
O Database Webhook dispara a cada alteração da linha, inclusive de campos que não importam. Compare record com old_record antes de enviar.
O bot responde a si mesmo
Mensagens enviadas pela própria instância também chegam, com fromMe: true. O filtro do exemplo evita que a resposta do bot gere outra.
Erros 401, 404 e 502 no envio
401: chave errada ou secret com outro nome. 404: instance não bate com o nome no painel. 502: a entrega ao WhatsApp falhou, e o motivo vem em details.

Perguntas frequentes

Dá para chamar o Zapixo direto de um trigger do Postgres?

Não é o recomendado. O Database Webhook envia o formato do Supabase, não o do Zapixo, e a Edge Function é o lugar certo para a chave e para tratar erro.

Consigo mostrar as mensagens no app em tempo real?

Sim. Com a tabela gravada pela função, o Realtime do Supabase avisa o app a cada insert, respeitando o RLS da tabela.

O webhook do Zapixo tem assinatura?

Não. Por isso a função confere um segredo na URL, já que ela é publicada sem verificação de JWT.

Consigo enviar imagem pela Edge Function?

Sim, pelo endpoint /api/v1/messages/media, com mediaUrl (link público do arquivo, que pode vir do Storage) e caption opcional.

O trial tem limite de mensagens?

Tem. Ao atingir o limite, a API responde 402 com o link para assinar.

Leia também