Raphael Serafim· Publicado em 10 de setembro de 2026· 9 min de leitura

Vários atendentes no mesmo número de WhatsApp: como fazer pela API

Um número, uma conversa por cliente e uma equipe inteira atendendo — sem duas pessoas respondendo a mesma coisa. Como distribuir, travar a conversa por atendente, transferir e passar do bot para o humano sem o cliente repetir tudo.

Ver como Markdown

O aplicativo do WhatsApp não foi feito para equipe. Ele tem limite de dispositivos conectados, não distribui conversa, não registra quem respondeu o quê e não impede duas pessoas de responderem a mesma coisa.

Pela API o problema muda de natureza: as mensagens chegam no seu sistema, e você decide quem atende.

O desenho

Cliente  →  WhatsApp  →  webhook  →  seu sistema
                                        ↓
                                   distribuição
                                        ↓
                            atendente 1, 2, 3…
                                        ↓
                            resposta  →  API  →  Cliente

Para o cliente, existe uma conversa só, com um número só. Toda a estrutura de equipe é invisível — e é assim que tem que ser.

As tabelas mínimas

CREATE TABLE conversas (
  id             BIGSERIAL PRIMARY KEY,
  numero         VARCHAR(20) UNIQUE NOT NULL,
  atendente_id   BIGINT,                       -- NULL = na fila
  status         VARCHAR(20) DEFAULT 'aberta', -- aberta|pendente|fechada
  fila           VARCHAR(30) DEFAULT 'geral',
  atribuida_em   TIMESTAMPTZ,
  ultima_em      TIMESTAMPTZ
);

CREATE TABLE mensagens (
  id           BIGSERIAL PRIMARY KEY,
  conversa_id  BIGINT NOT NULL,
  direcao      VARCHAR(10) NOT NULL,    -- entrada|saida
  atendente_id BIGINT,                  -- quem respondeu
  texto        TEXT,
  message_id   VARCHAR(80) UNIQUE,      -- idempotência
  criada_em    TIMESTAMPTZ DEFAULT NOW()
);

O numero UNIQUE em conversas é o que garante uma conversa por cliente. O message_id UNIQUE evita mensagem duplicada quando o webhook reentrega.

Receber e distribuir

async function aoReceber(msg) {
  const conversa = await obterOuCriarConversa(msg.from);

  await db.mensagens.criar({
    conversa_id: conversa.id,
    direcao: 'entrada',
    texto: msg.text?.body,
    message_id: msg.id,          // duplicata falha aqui, e tudo bem
  });

  // Conversa já atribuída continua com quem está atendendo.
  // Trocar de atendente no meio é péssima experiência.
  if (conversa.atendente_id) {
    return notificarAtendente(conversa.atendente_id, conversa.id);
  }

  const atendente = await escolherAtendente(conversa.fila);
  if (!atendente) {
    await db.conversas.atualizar(conversa.id, { status: 'pendente' });
    return avisarForaDoHorario(msg.from);
  }

  await atribuir(conversa.id, atendente.id);
}

A trava que evita resposta duplicada

Esta é a parte que dá problema em produção se for feita ingenuamente:

// ERRADO: dois atendentes passam pelo "if" ao mesmo tempo
if (!conversa.atendente_id) {
  await db.conversas.atualizar(conversa.id, { atendente_id: meuId });
}

// CERTO: a condição está no próprio UPDATE, e o banco resolve
async function atribuir(conversaId, atendenteId) {
  const r = await db.query(
    `UPDATE conversas
        SET atendente_id = $1, atribuida_em = NOW(), status = 'aberta'
      WHERE id = $2 AND atendente_id IS NULL
      RETURNING id`,
    [atendenteId, conversaId],
  );
  return r.rowCount > 0;   // false = outro atendente pegou primeiro
}

O WHERE atendente_id IS NULL dentro do UPDATE é o que torna a operação atômica. Sem isso, dois atendentes abrem a mesma conversa e o cliente recebe duas respostas — que é a reclamação número um de quem monta multiatendimento na pressa.

Distribuir com critério

async function escolherAtendente(fila) {
  const disponiveis = await db.query(
    `SELECT a.id, COUNT(c.id) AS abertas
       FROM atendentes a
       LEFT JOIN conversas c
              ON c.atendente_id = a.id AND c.status = 'aberta'
      WHERE a.online = true AND $1 = ANY(a.filas)
      GROUP BY a.id
     HAVING COUNT(c.id) < a.limite_simultaneo
      ORDER BY abertas ASC, a.ultima_atribuicao ASC NULLS FIRST
      LIMIT 1`,
    [fila],
  );
  return disponiveis.rows[0] ?? null;
}

Duas coisas importam mais que o algoritmo:

limite_simultaneo — atendente com 15 conversas abertas não atende nenhuma bem. Um teto por pessoa protege a qualidade.

Desempate por ultima_atribuicao — sem ele, quem fecha rápido recebe tudo, e quem enrola fica livre. O desempate distribui de forma justa.

Transferir sem fazer o cliente repetir

async function transferir(conversaId, deId, paraId, motivo) {
  await db.conversas.atualizar(conversaId, {
    atendente_id: paraId,
    atribuida_em: new Date(),
  });

  await db.eventos.criar({
    conversa_id: conversaId, tipo: 'transferencia',
    de: deId, para: paraId, motivo,
  });

  // O que evita a pior frase do atendimento: "me conta de novo?"
  const resumo = await db.mensagens.ultimas(conversaId, 20);
  await notificarAtendente(paraId, { conversaId, resumo, motivo });

  await enviar(conversa.numero,
    'Vou te passar para quem cuida disso, já com tudo que conversamos aqui. 👍');
}

Avisar o cliente da transferência e entregar o histórico ao novo atendente. Faltando qualquer um dos dois, a transferência vira a experiência que todo mundo odeia.

Do bot para o humano

O desenho mais eficiente combina os dois: o bot faz a triagem e resolve o repetitivo; a pessoa entra quando precisa.

async function aoReceber(msg) {
  const conversa = await obterOuCriarConversa(msg.from);

  // Já está com humano? O bot não interfere mais. Ponto.
  if (conversa.atendente_id) return encaminharParaAtendente(conversa, msg);

  const r = await bot.responder(conversa.id, msg.text.body);

  if (r.querHumano) {
    const atendente = await escolherAtendente(r.fila ?? 'geral');
    if (atendente) {
      await atribuir(conversa.id, atendente.id);
      await notificarAtendente(atendente.id, {
        conversaId: conversa.id,
        resumo: r.resumoDaConversa,   // o bot entrega o contexto
      });
      return enviar(msg.from, 'Já te conecto com alguém do time. 👋');
    }
    await db.conversas.atualizar(conversa.id, { status: 'pendente' });
    return enviar(msg.from, 'Todo mundo está ocupado agora. Assim que liberar, te chamo aqui.');
  }

  await enviar(msg.from, r.texto);
}

A primeira linha é a regra mais importante: depois que a conversa foi para um humano, o bot não volta a responder. Bot que interrompe atendimento humano é pior que não ter bot.

Como montar o bot está em chatbot de IA com a API da OpenAI — e o [HUMANO] daquele artigo é exatamente o querHumano daqui.

Horário e fila de espera

async function avisarForaDoHorario(numero) {
  if (dentroDoHorario()) {
    return enviar(numero, 'Recebemos sua mensagem! Já te respondo. 😊');
  }
  return enviar(numero,
    'Recebemos sua mensagem! Nosso atendimento é de segunda a sexta, das 8h às 18h. ' +
    'Respondo assim que abrirmos.');
}

Expectativa clara vale mais que resposta rápida. Cliente que sabe quando será atendido não manda mais cinco mensagens perguntando se tem alguém aí.

Métricas que dizem alguma coisa

NúmeroO que revela
Tempo até a primeira respostaO que o cliente sente
Tempo até resolverA eficiência real
Conversas por atendenteDistribuição justa
Taxa de transferênciaSe a triagem está errando
Pendentes no picoSe falta gente

Taxa de transferência alta quase sempre significa fila mal desenhada, não atendente ruim.

E se você já usa uma ferramenta pronta

Se a sua necessidade é atendimento e não um módulo dentro do seu produto, integrar com o Chatwoot entrega caixa compartilhada, atribuição e histórico prontos.

Construir faz sentido quando o atendimento precisa viver dentro do sistema que você já vende — com o cliente, o pedido e o histórico na mesma tela. É o caso típico de quem entrega software sob medida.

Conclusão

Multiatendimento não é recurso do WhatsApp: é o seu sistema decidindo quem responde. As peças são poucas — uma conversa por número, atribuição atômica, transferência com histórico e bot que sai de cena quando o humano entra.

A que mais economiza retrabalho é a trava atômica. É uma linha de SQL, e sem ela o cliente recebe duas respostas diferentes da mesma empresa.

Pronto para automatizar seu WhatsApp?

Crie sua conta gratuita e comece a enviar mensagens pela API em minutos.

Começar grátis

Perguntas frequentes

Como colocar vários atendentes no mesmo número de WhatsApp?+

Pela API. As mensagens chegam num webhook e o seu sistema decide quem atende, distribuindo as conversas entre os atendentes conectados. Todos respondem pelo mesmo número, e para o cliente existe uma conversa só — o que o aplicativo comum não permite.

Qual o limite de atendentes num número?+

Pela API não há o limite de dispositivos do aplicativo, porque quem conversa com o WhatsApp é o seu sistema, não os aparelhos. O limite prático passa a ser o volume de mensagens que o número comporta e a capacidade da sua equipe.

Como evitar que dois atendentes respondam a mesma conversa?+

Com trava de atribuição no banco. A conversa é atribuída a um atendente numa operação atômica, e a interface só permite responder quem está com ela. Sem isso, duas pessoas abrem a mesma conversa ao mesmo tempo e o cliente recebe duas respostas diferentes.

Como transferir uma conversa entre atendentes?+

Trocando a atribuição e preservando o histórico. O ponto importante é passar junto o resumo do que já foi tratado, para o cliente não precisar repetir. Transferência que faz o cliente contar tudo de novo é a maior reclamação de atendimento em qualquer canal.

Dá para o bot atender primeiro e passar para o humano?+

Sim, e é o desenho mais eficiente. O bot faz a triagem e resolve o que é repetitivo; quando identifica que precisa de gente, marca a conversa para atendimento humano e entrega o histórico ao atendente. O erro é o bot insistir depois de a pessoa já ter pedido um humano.

Continue lendo