← Blog

Como integrar WhatsApp com seu CRM

O vendedor conversa com o cliente pelo WhatsApp do celular. O CRM registra que o negócio está em "proposta enviada". As duas informações nunca se encontram — e quando o vendedor sai da empresa, o histórico vai embora com o aparelho dele.

Integrar WhatsApp e CRM resolve isso, mas a maioria das tentativas trava no mesmo ponto: o telefone não é uma chave confiável. Este guia mostra o desenho que funciona, independente de qual CRM você usa.

O problema central: o telefone é uma chave ruim

Antes de qualquer código, entenda por que a integração ingênua falha.

O mesmo cliente pode existir no seu CRM como 11987654321, (11) 98765-4321, +5511987654321 e 5511987654321. O WhatsApp devolve um formato próprio. Nenhum bate com o outro em uma comparação literal.

Pior: o nono dígito. Números de celular brasileiros ganharam um 9 na frente, mas o WhatsApp nem sempre o usa internamente para números antigos. O contato que você cadastrou como 5511987654321 pode chegar no webhook como 551187654321. São o mesmo telefone e não são a mesma string.

Isso produz o sintoma mais comum de integração mal feita: cadastro duplicado. Toda conversa cria um contato novo, e em três meses o CRM tem cinco versões do mesmo cliente.

A solução: normalize antes de comparar

Guarde uma forma canônica, sempre, e compare por ela:

function normalizarTelefone(bruto) {
  let d = String(bruto).replace(/\D/g, "");

  // remove o 55 do país para trabalhar com DDD + número
  if (d.startsWith("55") && d.length > 11) d = d.slice(2);

  // 11 dígitos (DDD + 9 + 8): remove o nono dígito para a forma canônica
  if (d.length === 11 && d[2] === "9") {
    d = d.slice(0, 2) + d.slice(3);
  }

  // resultado: DDD + 8 dígitos, ex.: "1187654321"
  return d.length === 10 ? d : null;
}

Guarde essa forma canônica em uma coluna indexada, ao lado do telefone original. A busca passa a ser exata e o duplicado desaparece.

Cuidado: essa normalização vale para celular brasileiro. Se você atende outros países, use uma biblioteca de telefone em vez de regra manual — a variedade de formatos derruba qualquer heurística caseira.

Antes de gravar um número novo, vale confirmar que ele existe no WhatsApp. O validador de número resolve o caso pontual; em volume, trate o retorno de erro do envio.

As duas direções da integração

Integração de CRM tem dois sentidos, e eles são independentes. Você pode implementar um sem o outro.

Direção 1: do CRM para o WhatsApp

O CRM manda mensagem quando algo acontece no funil. Exemplos:

Evento no CRMMensagem
Negócio criado"Recebemos seu contato, retornamos em breve"
Proposta enviada"Enviamos a proposta no seu e-mail, deu para ver?"
Negócio ganho"Fechado! Próximos passos: ..."
Negócio parado há X dias"Ainda tem interesse? Qualquer dúvida, é só responder"

A implementação é a mesma de qualquer disparo por evento: o CRM chama um webhook seu, você enfileira, o worker envia.

// endpoint que o CRM chama quando o negócio muda de etapa
export async function POST(request) {
  const evento = await request.json();

  const contato = await buscarContatoPorTelefoneCanonico(
    normalizarTelefone(evento.telefone)
  );
  if (!contato) return Response.json({ ok: true, ignorado: "sem telefone" });

  await enfileirarMensagem({
    chaveUnica: `negocio-${evento.negocioId}-etapa-${evento.etapa}`,
    instancia: "comercial",
    numero: contato.telefoneWhatsapp,
    texto: montarMensagemDaEtapa(evento.etapa, contato),
  });

  return Response.json({ ok: true });
}

A chaveUnica com o número do negócio e a etapa impede o problema mais chato dessa integração: o CRM que dispara o webhook duas vezes na mesma mudança, e o cliente que recebe a mesma mensagem em duplicata.

Um cuidado de funil: mensagem automática para negócio parado é a que mais gera descadastro. Ela é, na prática, marketing — e a base legal é outra. Se for usar, mande uma vez, não em série, e ofereça saída clara. O tema está em LGPD em automação de WhatsApp.

Direção 2: do WhatsApp para o CRM

A conversa vira registro no CRM. É a direção que resolve o problema do histórico perdido.

O webhook de recebimento entrega cada mensagem; você identifica o contato e registra:

async function aoReceberMensagem(evento) {
  const telefone = normalizarTelefone(extrairTelefone(evento));
  const texto = extrairTexto(evento);

  let contato = await buscarNoCrmPorTelefoneCanonico(telefone);

  if (!contato) {
    // ninguém conhecido escreveu: cria lead em vez de descartar
    contato = await criarLeadNoCrm({
      telefoneCanonico: telefone,
      origem: "whatsapp",
      nome: extrairNomePerfil(evento) ?? "Contato WhatsApp",
    });
  }

  await registrarAtividade(contato.id, {
    tipo: "mensagem_whatsapp",
    direcao: "recebida",
    conteudo: texto,
    ocorridoEm: new Date(),
  });
}

Duas decisões dentro desse trecho:

Contato desconhecido vira lead. Descartar mensagem de quem não está no CRM significa jogar fora oportunidade. Crie o lead marcado com a origem.

Registre também o que você envia. Só as recebidas contam metade da história. Ao confirmar um envio, grave a atividade na outra direção — é o que permite ao vendedor abrir o CRM e ler a conversa inteira.

Onde a integração costuma quebrar

Volume de atividade. Uma conversa de trinta mensagens vira trinta registros. Em alguns CRMs isso polui a linha do tempo a ponto de esconder o que importa. Considere agrupar por conversa: uma atividade por dia por contato, com as mensagens dentro.

Limite de API do CRM. Quase todo CRM tem cota de requisições, e ela costuma ser mais apertada que a do WhatsApp. Sua fila precisa respeitar o limite do CRM também, não só o de 60 requisições por minuto do envio. O padrão de fila e retentativa está em rate limit e fila.

Contato sem telefone. Muito lead entra só com e-mail. A integração precisa ignorar em silêncio, não falhar.

Vendedor que responde pelo celular pessoal. Se ele sai da conversa oficial e continua no aparelho dele, o registro para. Isso é problema de processo, não de código — mas é a causa mais comum de integração que "não funciona".

Se você não quer escrever backend

Boa parte desse desenho cabe em uma ferramenta de automação visual, e para CRMs populares já existem nós prontos. Um fluxo no n8n faz o caminho inteiro: recebe o webhook do WhatsApp, procura o contato no CRM, cria se não existir, registra a atividade e responde. O Make resolve de forma parecida.

A vantagem é ajustar o fluxo sem redeploy. A desvantagem aparece no volume: com muita mensagem, o custo por operação dessas plataformas cresce e um worker próprio fica mais barato.

Para atendimento com várias pessoas, vale olhar o Chatwoot — ele já resolve caixa compartilhada e histórico, e aí o CRM recebe só o resumo.

Ordem de implementação

Não faça as duas direções de uma vez. Uma ordem que reduz retrabalho:

  1. Normalize os telefones que já existem no CRM. Antes de qualquer integração — é aqui que os duplicados atuais aparecem, e é melhor limpar antes de dobrar a base.
  2. Registre o que chega. Direção 2 primeiro: valor imediato, risco zero, ninguém recebe nada.
  3. Registre o que sai.
  4. Dispare um evento só. O de maior volume, geralmente "negócio criado".
  5. Expanda por evento, medindo descadastro a cada novo.

O passo 1 é o que quase todo mundo pula, e é o que faz a integração parecer quebrada depois: sem normalizar a base existente, os primeiros registros caem em contatos duplicados e ninguém encontra nada.

Resumo

Integrar WhatsApp e CRM é menos sobre a API e mais sobre identidade: o mesmo cliente precisa ser a mesma linha nos dois sistemas. Resolvido o telefone canônico, o resto é encanamento — webhook de um lado, fila do outro, chave única para não duplicar.

Comece registrando o que chega, que é onde o ganho aparece primeiro e o risco é nenhum. A referência dos endpoints está na documentação.