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.
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úmero | O que revela |
|---|---|
| Tempo até a primeira resposta | O que o cliente sente |
| Tempo até resolver | A eficiência real |
| Conversas por atendente | Distribuição justa |
| Taxa de transferência | Se a triagem está errando |
| Pendentes no pico | Se 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átisPerguntas 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
Como criar um chatbot de IA com a API da OpenAI para responder no WhatsApp
Um webhook, uma chamada à API da OpenAI e uma resposta pela WAME API: o código completo de um chatbot de IA que atende no WhatsApp, Instagram e Messenger. Com memória por contato, controle de custo e o que fazer quando a IA não deve responder.
Cobrança por Pix dentro do WhatsApp pela API: como enviar e o que muda na conversão
Mandar o código Pix no WhatsApp resolve o pior ponto da cobrança digital: o cliente não precisa sair do app. Como enviar a cobrança pela API, tratar a confirmação e evitar os erros que transformam a facilidade em suporte.
Erros da API do WhatsApp: o que cada um significa e como tratar
A mensagem não saiu e o log diz apenas 'erro ao enviar'. Os erros que você vai encontrar de verdade — janela fechada, número inválido, template não aprovado, limite atingido, instância caída — e o tratamento certo para cada um.