Clínica Lindomar Delgado · Guia operacional
Serviço que liga o atendimento de WhatsApp (SymplesDesk) ao CRM (Pipedrive) e grava tudo em um banco de dados próprio, em tempo real. Esta página explica o que cada parte faz e o que é preciso configurar para cada uma funcionar — sem precisar abrir o repositório.
Atualizado em 12/08/2026 · versão em produção, validada ponta a ponta
A configuração de cada médico (funil, etapa, responsável, canal de WhatsApp) mora em três
tabelas do banco (integration_channels, integration_pipelines e
integration_pipeline_steps) — trocar de ambiente de teste para produção é editar
essas linhas e o .env, nunca o código.
Além dos 4 fluxos, o serviço mantém o quadro de Oportunidades do SymplesDesk espelhando o CRM nos dois sentidos — hoje em dois funis: o de Leads, das SDRs, e o de Consulta, das Concierges — e encerra a conversa 24h depois de um lead ser dado como perdido. Essas partes não são "Fluxo 5 e 6": nasceram depois e funcionam em paralelo aos quatro.
A regra mais importante de todo o sistema:
+55 (32) 99811-7881 e 3298117881 são a mesma pessoa.Quando um paciente inicia conversa no WhatsApp, o SymplesDesk abre um ticket e avisa o serviço, que cria o lead no funil certo do médico certo — sem duplicar paciente nem ciclo.
55+DDD+número.telefone - sigla,
responsável, etapa e etiqueta vindos da config.<URL-do-serviço>/webhooks/symplesdesk. Importante: esse evento deve apontar
para uma URL só — dois destinos criariam deal duplicado.integration_channels no banco (canal → médico →
funil/etapa/responsável) — o SQL pronto está em
Como adicionar um novo canal.PIPEDRIVE_BASE_URL e PIPEDRIVE_API_TOKEN.Quando a equipe marca um deal como perdido ou ganho no Pipedrive, o serviço reage:
Deal perdido no Pipedrive: [título] / Motivo: [motivo da perda] — e o
ciclo vira closed_lost no banco. A conversa NÃO fecha neste momento: ela
fecha 24 horas depois, se o paciente não voltar (ver
Fechamento da conversa).closed_won) e a conversa até o momento do
ganho entra no deal na hora (ver Fluxo 4). Não fecha a conversa — o atendimento
pós-ganho continua no WhatsApp (decisão da clínica).deal, ações create + change, e adicionar a URL da
API: <URL-do-serviço>/webhooks/pipedrive, preenchendo usuário e senha (Basic
Auth) com os mesmos valores do ambiente.person, ação change, mesma URL e mesma Basic Auth. Sempre que
alguém editar o nome de uma pessoa no Pipedrive, o serviço atualiza o nome no banco
(só o nome — o telefone é a identidade do contato e nunca muda por aqui).SYMPLESDESK_TOKEN_T e SYMPLESDESK_TOKEN_A) — já validados contra a
produção em 22/07/2026.DISCORD_ERROR_WEBHOOK_URL para os alertas de falha.A automação só cria deal no funil de Leads (Fluxo 1). Nos funis de Consulta e Procedimentos, os deals são criados à mão pela equipe Concierge — este fluxo os captura para o banco, rastreando a jornada completa do paciente.
integration_pipelines com os funis mapeados
(Leads, Consulta e Procedimentos por médico) — o SQL pronto está em
Como adicionar um novo canal.Todas as mensagens trocadas no WhatsApp aparecem dentro do deal do paciente no Pipedrive, como uma atividade por dia de conversa. São duas partes:
symplesdesk_messages, vinculada ao contato certo
(pelo ticket ou pelo telefone). Mensagens repetidas (reenvio) e apagadas não duplicam nem entram.CONVERSATION_SYNC_CRON, sem mexer em código: o job agrupa as mensagens pendentes
por paciente e por dia (incluindo o próprio dia até o horário do disparo) e cria uma
Activity "Whatsapp - DD/MM/AAAA" por dia de conversa — somente em deals ABERTOS dos
funis de Leads e Consulta. Paciente sem deal aberto: as mensagens aguardam (nada se perde) e
entram no próximo deal.Formato da nota (mensagens do mesmo minuto são agrupadas, com o horário uma vez no fim; atendente aparece pelo nome):
Oi, tudo bem? Queria marcar. 15:07 Gabriel: Oi! Claro, vou te passar as opções. 15:07
<URL-do-serviço>/webhooks/symplesdesk-newmessage, colando no campo "Token de
autenticação" o mesmo valor de SYMPLESDESK_WEBHOOK_TOKEN do ambiente.PIPEDRIVE_ACTIVITY_TYPE_WHATSAPP_KEY — a chave do
tipo de atividade "Whatsapp" na conta do Pipedrive (sem ela o job falha de propósito, para
nunca criar atividade de tipo errado).As SDRs trabalham no quadro de Oportunidades do SymplesDesk, sem precisar abrir o Pipedrive. Cada deal de Leads tem um card espelho, e os dois lados se acompanham nos dois sentidos: mover o card move o deal, e mover o deal move o card.
| A SDR faz no card | Acontece no Pipedrive |
|---|---|
| Arrasta para outra etapa | o deal muda de etapa no funil |
| Marca como ganho | o deal vira ganho |
| Marca como perdido | o deal vira perdido, com o motivo |
| Renomeia o card | o nome da pessoa é corrigido no CRM |
| Alguém faz no Pipedrive | Acontece no quadro |
|---|---|
| Move o deal de etapa | o card acompanha |
| Ganha ou perde o deal | o card fecha com o mesmo resultado |
| Corrige o nome da pessoa | o card é renomeado |
| Reabre um deal fechado | o card volta a abrir |
<URL-do-serviço>/webhooks/oportunidade e colando no campo "Token de
autenticação" o valor de SYMPLESDESK_OPORTUNIDADE_WEBHOOK_SECRET.integration_pipeline_steps) — ver a seção
Mapa das etapas. Sem a tradução, o movimento é ignorado com registro:
o sistema nunca chuta uma etapa.symplesdesk_responsible_user_id): é por esse campo que cada uma filtra o quadro
compartilhado para ver só os cards dela.symplesdesk_lost_reason_id): o fornecedor recusa fechar card como perdido
sem motivo. O motivo real continua no Pipedrive e na nota — este é só o exigido pela tela.Quando um lead é ganho, o Pipedrive cria sozinho o negócio no funil de Consulta — isso já acontecia e continua igual. A novidade é que esse negócio agora também ganha um card, no funil "Funil de Consulta - Concierge" do quadro, com a Concierge do médico como dona. A Concierge passa a trabalhar no mesmo lugar que a SDR, sem abrir o Pipedrive.
| Card de Leads (SDR) | Card de Consulta (Concierge) | |
|---|---|---|
| Dona do card | a SDR do médico | a Concierge do médico |
| Nome do card | montado pelo sistema: nome - sigla | cópia exata do nome do negócio: CONSULTA DE nome |
| Etapas | as do funil de Leads | as do funil de Consulta |
| Renomear o card | corrige o nome da pessoa no CRM | não propaga nada — ver abaixo |
Mover o card de etapa move o negócio no funil de Consulta, e vice-versa. Ganhar ou perder o card fecha o negócio com o mesmo resultado, e reabrir também acompanha. É a mesma mão dupla dos cards de Leads — só o renomear ficou de fora.
symplesdesk_concierge_user_id): é o campo que liga esta parte. Vazio,
nenhum card de Consulta é criado — e o resto do sistema segue normal.integration_pipeline_steps com pipeline_type = 'consulta') — ver a
seção Mapa das etapas.Quando um lead é dado como perdido, a conversa dele no WhatsApp não fecha na hora: fecha 24 horas depois. A janela existe para o paciente ter tempo de responder — muita reversão acontece nesse intervalo.
Ao fechar, o sistema deixa uma nota interna no atendimento: "Conversa encerrada automaticamente — lead perdido há 24h." O paciente não recebe nada.
Se alguém encerra o atendimento manualmente no SymplesDesk, o sistema fica sabendo e registra — e por isso não tenta fechar de novo o que já está fechado.
<URL-do-serviço>/webhooks/atendimento-finalizado, com o valor
de SYMPLESDESK_ATENDIMENTO_WEBHOOK_SECRET no campo "Token de autenticação".CLOSE_LOST_CRON (horário) e CLOSE_LOST_MAX_PER_RUN
(teto) — ver Ligar e desligar.Os dois sistemas nomeiam as etapas de formas diferentes e usam números próprios. A tabela
integration_pipeline_steps é a tradução entre eles — e é ela que faz o card
acompanhar o deal e vice-versa.
| Etapa | No quadro (SymplesDesk) | Dr. Thiago | Dr. Augusto |
|---|---|---|---|
| Cliente Potencial | 52 | 46 | 37 |
| Em conversa | 50 | 27 | 36 |
| Aguardando Pagamento | 48 | 30 | 39 |
| Etapa | No quadro (SymplesDesk) | Dr. Thiago | Dr. Augusto |
|---|---|---|---|
| Consulta Agendada | 57 | 47 | 55 |
| Reagendamento | 58 | 49 | 56 |
| Consulta Confirmada | 59 | 48 | 57 |
O quadro é um só para os dois médicos — por isso a mesma etapa (52, por exemplo) traduz para funis diferentes conforme o médico do deal. Quem separa o trabalho de cada SDR e de cada Concierge é o filtro por responsável, não o quadro.
Toda automação pode ser desligada sem mexer em código e sem derrubar o resto. Útil para manutenção, teste, ou quando a clínica quer pausar um comportamento.
| Desligar | Como | Efeito |
|---|---|---|
| Um médico inteiro | integration_channels.is_active = false |
o canal some da configuração; nada daquele médico é processado |
| Criação de cards de Leads | symplesdesk_responsible_user_id = NULL |
nenhum card novo de Leads é criado; os existentes seguem funcionando |
| Criação de cards de Consulta | symplesdesk_concierge_user_id = NULL |
nenhum card novo de Consulta é criado; o funil de Leads não é afetado |
| Fechar card na perda | symplesdesk_lost_reason_id = NULL |
o deal é perdido normalmente, o card fica aberto |
| Fechamento de 24h | CLOSE_LOST_MAX_PER_RUN=0 |
a verificação roda e não fecha nada |
| Conversa diária no deal | CONVERSATION_SYNC_CRON=0 0 30 2 * |
30 de fevereiro nunca chega — o job nunca dispara |
| Tarefas de NPS | remover PIPEDRIVE_SURGERY_DATE_FIELD_KEY |
o job roda sem fazer nada |
Quem vai apenas integrar com a API não precisa desta seção — toda interação de fora é pelos endpoints e webhooks. Os blocos abaixo são de quem opera o serviço: variáveis de ambiente (segredos) e os webhooks apontados para a URL pública.
.env)Os valores reais nunca ficam no código nem nesta página — vivem só no .env do servidor.
| Variável | Obrigatória? | Para quê |
|---|---|---|
DATABASE_URL | Sim | Conexão com o Postgres (fonte da verdade) |
PIPEDRIVE_BASE_URL | Sim | Domínio da conta Pipedrive (teste ou produção) |
PIPEDRIVE_API_TOKEN | Sim | Token da API do Pipedrive |
PIPEDRIVE_WEBHOOK_BASIC_USER / _PASS | Sim | Usuário/senha que o webhook do Pipedrive usa para se autenticar |
SYMPLESDESK_TOKEN_T / _A | Fluxo 2 | Tokens da API de fechar conversa, um por canal/médico |
SYMPLESDESK_WEBHOOK_TOKEN | Fluxo 4 | Token que autentica o webhook de mensagem (sem ele a rota recusa tudo) |
PIPEDRIVE_ACTIVITY_TYPE_WHATSAPP_KEY | Fluxo 4 | Chave do tipo de atividade "Whatsapp" na conta |
PIPEDRIVE_PROCEDURE_FIELD_KEY | Produção | Campo personalizado "Procedimento" do deal (não existe na conta de teste) |
SYMPLESDESK_OPORTUNIDADE_WEBHOOK_SECRET | Cards | Token que autentica o webhook de Oportunidade (sem ele a rota recusa tudo) |
SYMPLESDESK_ATENDIMENTO_WEBHOOK_SECRET | Fechamento | Token do webhook "Atendimento finalizado" |
PIPEDRIVE_SURGERY_DATE_FIELD_KEY | NPS | Campo "Data da cirurgia"; ausente = tarefas de NPS desligadas |
CONVERSATION_SYNC_CRON | Opcional | Horário do job da conversa (cron, fuso Brasília; padrão 0 19 * * * = 19h) |
CLOSE_LOST_CRON | Opcional | Quando checar conversas a fechar (padrão 15 * * * * = de hora em hora, aos :15) |
CLOSE_LOST_MAX_PER_RUN | Opcional | Teto de fechamentos por rodada (padrão 25; 0 desliga) |
NPS_SYNC_CRON | Opcional | Horário do job de NPS (padrão 0 7 * * * = 7h) |
DISCORD_ERROR_WEBHOOK_URL | Opcional | Alertas operacionais de erro |
PORT | Opcional | Porta HTTP (padrão 3000) |
O banco Postgres é parte interna do serviço e é imutável para terceiros: somente o administrador da integração cria as tabelas e mantém a configuração de canais e funis. Quem consome a API não precisa — nem consegue — acessar o banco; tudo acontece pelos endpoints. (Administrador: o passo a passo do banco está em Adicionar um médico e em Colocando no ar.)
São 3 webhooks — cada um é criar na tela do sistema de origem e adicionar a URL da API. O passo
a passo campo a campo de cada tela está no guia docs/WEBHOOKS.md do repositório.
| Onde criar | Evento | URL da API | Autenticação |
|---|---|---|---|
| SymplesDesk (2 canais) | Mensagem criada | /webhooks/symplesdesk-newmessage | Token de autenticação |
| Pipedrive (Webhooks v2) | deal: create + change | /webhooks/pipedrive | Usuário e senha (Basic Auth) |
| Pipedrive (Webhooks v2) | person: change (sync de nome) | /webhooks/pipedrive | Usuário e senha (Basic Auth) |
| SymplesDesk (2 canais) | Novo atendimento | /webhooks/symplesdesk | — |
| SymplesDesk (2 canais) | Oportunidade (card) | /webhooks/oportunidade | Token de autenticação |
| SymplesDesk (2 canais) | Atendimento finalizado | /webhooks/atendimento-finalizado | Token de autenticação |
Somente o administrador da integração executa estes passos — o banco é imutável para qualquer outra pessoa. Toda a configuração por médico é dado, não código: adicionar um médico novo é inserir linhas em duas tabelas, guardar um token no ambiente e criar os webhooks do canal. Nenhum deploy de código novo.
public.integration_channelsTroque os valores em MAIÚSCULAS pelos reais (a tabela abaixo do SQL explica onde encontrar cada um):
INSERT INTO integration_channels
(doctor_code, doctor_name, symplesdesk_account_id, symplesdesk_whatsapp_id,
symplesdesk_base_url, symplesdesk_api_id, symplesdesk_token_secret_name,
pipedrive_owner_id, pipedrive_pipeline_id, pipedrive_stage_id,
pipedrive_label_id, is_active)
VALUES
('SIGLA', 'NOME DO MÉDICO', 'ACCOUNT_ID_SYMPLESDESK', 'WHATSAPP_ID',
'https://serv2api.simplesdesk.com.br', 'API_ID_DA_CONFIG_DE_API',
'SYMPLESDESK_TOKEN_SIGLA', 'OWNER_ID_PIPEDRIVE', 'ID_FUNIL_LEADS',
'ID_ETAPA_ENTRADA', 'ID_ETIQUETA', true);
| Campo | O que é / onde encontrar |
|---|---|
doctor_code | Sigla única do médico (1 letra, ex.: 'T'). Aparece no título do deal (telefone - SIGLA) e amarra todas as outras configs. |
doctor_name | Nome de exibição (ex.: 'Dr. Thiago Delgado'). |
symplesdesk_account_id | ID da conta do canal no SymplesDesk (vem no payload dos webhooks do canal). |
symplesdesk_whatsapp_id | ID do canal de WhatsApp no SymplesDesk (ex.: '139'). É por ele que o Fluxo 1 descobre de qual médico é o lead. |
symplesdesk_base_url | Sempre 'https://serv2api.simplesdesk.com.br' (base da API externa). |
symplesdesk_api_id | ID (uuid) da configuração de API criada no SymplesDesk para este canal (tela APIs → a config de fechamento de conversa). Usado pelo Fluxo 2. |
symplesdesk_token_secret_name | Nome da variável de ambiente que guarda o token JWT dessa config — padrão 'SYMPLESDESK_TOKEN_SIGLA'. O token em si nunca vai no banco (só o nome da variável). |
pipedrive_owner_id | ID do usuário do Pipedrive que será o dono dos deals criados (Configurações → Usuários). |
pipedrive_pipeline_id / pipedrive_stage_id | ID do funil de Leads do médico e da etapa de entrada (aparecem na URL do Pipedrive ao abrir o funil/etapa). |
pipedrive_label_id | ID da etiqueta aplicada à pessoa (produção usa a etiqueta "WhatsApp"). Pode ser NULL se não houver. |
is_active | true para ligar o canal; false desliga sem apagar a linha. |
public.integration_pipelinesTrês linhas por médico — Leads (o funil onde o Fluxo 1 cria), Consulta e Procedimentos (funis que os Fluxos 2 e 3 rastreiam). Só o funil de Leads precisa de etapa:
INSERT INTO integration_pipelines
(doctor_code, pipeline_type, pipedrive_pipeline_id, pipedrive_stage_id, is_active)
VALUES
('SIGLA', 'leads', 'ID_FUNIL_LEADS', 'ID_ETAPA_ENTRADA', true),
('SIGLA', 'consulta', 'ID_FUNIL_CONSULTA', NULL, true),
('SIGLA', 'procedimentos', 'ID_FUNIL_PROCEDIMENTOS', NULL, true);
public.integration_pipeline_stepsSem esta tradução o card não acompanha o deal: o movimento é ignorado com registro, em silêncio. Uma linha por etapa, em cada funil que o médico novo vai usar (ver Mapa das etapas):
INSERT INTO integration_pipeline_steps
(doctor_code, pipeline_type, step_name, symplesdesk_step_id, pipedrive_stage_id, step_order, is_active)
VALUES
-- funil de Leads (SDR)
('SIGLA', 'leads', 'Cliente Potencial', '52', 'ID_ETAPA_ENTRADA', 1, true),
('SIGLA', 'leads', 'Em conversa', '50', 'ID_ETAPA_CONVERSA', 2, true),
('SIGLA', 'leads', 'Aguardando Pagamento', '48', 'ID_ETAPA_PAGAMENTO', 3, true),
-- funil de Consulta (Concierge)
('SIGLA', 'consulta', 'Consulta Agendada', '57', 'ID_ETAPA_AGENDADA', 1, true),
('SIGLA', 'consulta', 'Reagendamento', '58', 'ID_ETAPA_REAGENDA', 2, true),
('SIGLA', 'consulta', 'Consulta Confirmada', '59', 'ID_ETAPA_CONFIRMADA', 3, true);
As etapas do quadro (52, 50, 48 em Leads; 57, 58, 59 em Consulta) são as mesmas para
todos os médicos — o quadro é único. O que muda por médico são os ids das etapas do funil no
Pipedrive. A coluna pipeline_type é o que impede os números de se confundirem
entre um funil e outro — nunca deixe de preenchê-la.
No .env do serviço, crie a variável com o mesmo nome usado em
symplesdesk_token_secret_name, com o token JWT gerado na config de API do canal:
SYMPLESDESK_TOKEN_SIGLA=<token JWT gerado na tela de APIs do SymplesDesk>
<URL-do-serviço>/webhooks/symplesdesk-newmessage (com o token do ambiente), o
evento de novo atendimento → <URL-do-serviço>/webhooks/symplesdesk, o de
Oportunidade → <URL-do-serviço>/webhooks/oportunidade e o de
Atendimento finalizado → <URL-do-serviço>/webhooks/atendimento-finalizado.A configuração de canais e funis é carregada na inicialização (fica em memória para não consultar o banco a cada mensagem). Depois de inserir as linhas, reinicie o serviço para o canal novo valer.
telefone - SIGLA. Qualquer
erro fica registrado em integration_events.Se você vai integrar com a API, esta é a sua seção. Toda interação de fora é por HTTP — ninguém além do administrador acessa o banco.
| Endpoint | Autenticação | O que faz |
|---|---|---|
GET /health | — | Confirma que o serviço está no ar ({"ok":true}) |
POST /webhooks/symplesdesk | — (limitação da tela antiga) | Fluxo 1: entrada de lead |
POST /webhooks/pipedrive | Basic Auth | Fluxos 2, 3 e 5-B: status, captura e movimentação de deals + sync de nome do contato (person) |
POST /webhooks/symplesdesk-newmessage | Token no header | Fluxo 4: ingestão de cada mensagem |
POST /webhooks/oportunidade | Token no header | Cards: movimento, ganho, perda e renome feitos no quadro |
POST /webhooks/atendimento-finalizado | Token no header | Registra que alguém encerrou o atendimento na mão |
GET /docs | — | Esta página |
Todos os webhooks respondem 200 mesmo em erro interno (o erro fica registrado no banco,
em integration_events): se respondessem erro, o remetente reenviaria o evento e criaria
duplicatas. A exceção é autenticação inválida, que responde 401 — ou 404 nas duas
rotas mais novas, que não confirmam sequer a própria existência para quem não tem o token.
Cada resposta traz um outcome dizendo o que aconteceu (ex.: created,
duplicate_ticket, active_session_exists, ignored_event) — a
lista completa por endpoint está no docs/API.md do repositório.
.env completo → gera a URL pública fixa da API.db/schema.sql + db/seed_prod.sql
(limpo — os dados atuais são de teste).npm run backfill, com --dry-run antes para conferir os números).integration_events e os
alertas do Discord.