Clínica Lindomar Delgado · Guia operacional

Integração SymplesDesk ↔ Pipedrive

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

Visão geral

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.

Conceito central: pessoa ≠ ciclo

A regra mais importante de todo o sistema:

Entrada de lead (SDR)

gatilho: webhook "Novo atendimento" (NewTicket) do SymplesDesk

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.

Como funciona

  1. Confere se esse ticket já foi processado (reenvio do SymplesDesk não duplica nada).
  2. Identifica o médico pelo canal de WhatsApp que recebeu a mensagem (config no banco).
  3. Normaliza o telefone e acha ou cria o contato no banco.
  4. Se o contato já tem ciclo ativo com esse médico → não cria nada (evita deal duplicado).
  5. Se não tem: busca a pessoa no Pipedrive pelo telefone, em qualquer formato (+55, hífen, 9º dígito), varrendo até 300 candidatos. Quem já tem cadastro (com DDD válido) é vinculado — e o nome do cadastro preenche o banco. Cadastro sem DDD é ignorado de propósito (está errado no CRM): cria-se pessoa nova no formato correto 55+DDD+número.
  6. Cria o deal no funil de Leads do médico — título telefone - sigla, responsável, etapa e etiqueta vindos da config.
  7. Grava tudo no banco: ciclo ativo, deal, conversa e trilha de auditoria.

O que precisa para funcionar

  • Criar o webhook no SymplesDesk (um por canal), com o evento de novo atendimento marcado, e adicionar a URL da API: <URL-do-serviço>/webhooks/symplesdesk. Importante: esse evento deve apontar para uma URL só — dois destinos criariam deal duplicado.
  • Preencher a tabela integration_channels no banco (canal → médico → funil/etapa/responsável) — o SQL pronto está em Como adicionar um novo canal.
  • Definir no ambiente: PIPEDRIVE_BASE_URL e PIPEDRIVE_API_TOKEN.

Deal perdido / ganho

gatilho: webhook do Pipedrive (deal.updated, mudança de status)

Quando a equipe marca um deal como perdido ou ganho no Pipedrive, o serviço reage:

Como funciona

  1. Perdido: o serviço deixa uma nota interna no atendimento com o motivo —
    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).
  2. Ganho: atualiza o banco (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).
  3. Deal perdido que o banco ainda não conhece (criado à mão, ou anterior à integração) não é ignorado: o serviço busca a pessoa no Pipedrive, resolve o contato pelo telefone e fecha mesmo assim.
  4. Se a API do SymplesDesk falhar, o banco é atualizado mesmo assim e um alerta no Discord avisa a equipe (a conversa não fica esquecida aberta sem ninguém saber).
  5. O card da SDR também fecha, com o mesmo resultado do deal — ganho ou perdido (ver Cards de Oportunidade).
Mudou em 07/08/2026. Até então a conversa fechava na hora da perda, por uma configuração da tela do SymplesDesk (não pelo código). Isso encurtava a janela de recuperação do paciente. As duas configurações foram trocadas para "Manter aberto", e o fechamento passou a ser o de 24h.

O que precisa para funcionar

  • Criar um webhook no Pipedrive (Configurações → Ferramentas → Webhooks, versão 2): objeto 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.
  • Criar um 2º webhook no Pipedrive para o sync de nome do contato: objeto 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).
  • Definir no ambiente os tokens da API externa do SymplesDesk, um por canal (SYMPLESDESK_TOKEN_T e SYMPLESDESK_TOKEN_A) — já validados contra a produção em 22/07/2026.
  • Opcional: DISCORD_ERROR_WEBHOOK_URL para os alertas de falha.

Captura Concierge e movimentação

gatilho: o mesmo webhook do Pipedrive (deal.added e deal.updated)

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.

Como funciona

  1. Deal novo criado à mão (Consulta/Procedimentos): o serviço resolve o paciente pelo telefone da pessoa do Pipedrive e grava o deal no banco. Nunca captura no funil de Leads (esse é papel do Fluxo 1) e nunca cria nada no Pipedrive — só observa.
  2. Qualquer movimentação de deal (troca de etapa, troca de funil, reabertura): o banco sobrescreve a etapa atual do deal — ele espelha sempre o estado de agora, sem histórico de etapas.
  3. Deal desconhecido que se move: é capturado na hora, com a etapa atual. Se for um deal aberto no funil de Leads, também ganha um ciclo ativo no banco.
  4. Deal de funil que não é da integração (qualquer outro funil da conta) é ignorado.

O que precisa para funcionar

  • O mesmo webhook do Pipedrive do Fluxo 2 — um único webhook cobre os dois fluxos, nada extra pra criar.
  • Preencher a tabela integration_pipelines com os funis mapeados (Leads, Consulta e Procedimentos por médico) — o SQL pronto está em Como adicionar um novo canal.

Conversas do WhatsApp dentro do deal

gatilho: webhook "Mensagem criada" + job diário 19:00 + fechamento do deal

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:

Parte A — cada mensagem entra no banco

  1. O SymplesDesk envia um webhook a cada mensagem (recebida ou enviada), autenticado por token.
  2. O serviço grava a mensagem em symplesdesk_messages, vinculada ao contato certo (pelo ticket ou pelo telefone). Mensagens repetidas (reenvio) e apagadas não duplicam nem entram.
  3. Segurança: o bloco de configuração que o fornecedor ecoa no payload (contém tokens) é removido antes de gravar.

Parte B — a conversa vira Activity no deal (dois gatilhos)

  1. Todo dia às 19:00 (Brasília) — horário ajustável pela variável 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.
  2. Na hora do fechamento: quando um deal de Leads/Consulta é marcado como ganho ou perdido, toda a conversa pendente até aquele instante entra imediatamente nesse deal — sem esperar as 19h. (Ganho na SDR = a conversa completa até o ganho já fica no deal.)
  3. Cada mensagem sincronizada guarda no banco qual deal a recebeu e quando — uma mensagem nunca vai pra dois deals nem é enviada duas vezes.
  4. Se o serviço ficou fora do ar no horário, a próxima execução recupera os dias perdidos sozinha. Dias gigantes são divididos em partes ("Parte 1/2, 2/2…"). Falha em um paciente não derruba os demais e dispara alerta no Discord.

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

O que precisa para funcionar

  • Criar o webhook "Mensagem criada" nas 2 configurações de API do SymplesDesk (uma por canal) e adicionar a URL da API: <URL-do-serviço>/webhooks/symplesdesk-newmessage, colando no campo "Token de autenticação" o mesmo valor de SYMPLESDESK_WEBHOOK_TOKEN do ambiente.
  • Marcar somente o evento "Mensagem criada" na tela (outros eventos são ignorados com segurança, mas geram tráfego à toa).
  • Definir no 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).

Cards de Oportunidade — o quadro do SymplesDesk

gatilho: mão dupla — deal muda no Pipedrive, ou card muda no quadro

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.

Do card para o deal

A SDR faz no cardAcontece no Pipedrive
Arrasta para outra etapao deal muda de etapa no funil
Marca como ganhoo deal vira ganho
Marca como perdidoo deal vira perdido, com o motivo
Renomeia o cardo nome da pessoa é corrigido no CRM

Do deal para o card

Alguém faz no PipedriveAcontece no quadro
Move o deal de etapao card acompanha
Ganha ou perde o dealo card fecha com o mesmo resultado
Corrige o nome da pessoao card é renomeado
Reabre um deal fechadoo card volta a abrir

Quando o card aparece

  1. Lead novo: o card nasce junto com o deal, na etapa de entrada.
  2. Paciente antigo que volta a falar: o card é criado na hora da mensagem, já na etapa em que o deal está hoje — nunca na etapa de entrada, para o quadro não mentir sobre o estado do lead.
  3. Paciente que nunca voltou a falar fica sem card de propósito: sem conversa não há o que trabalhar no quadro.
Duas regras que evitam confusão. (1) O telefone nunca sobrescreve um nome. Quando o paciente ainda não tem cadastro, o card nasce com o telefone — mas assim que alguém escreve o nome de verdade, o telefone nunca mais volta por cima. (2) Eco não vira pingue-pongue. Toda mudança que chega é comparada com o estado atual: se já está igual, o sistema para. Foi o que impediu 326 idas e voltas desnecessárias em 30 horas.

O que precisa para funcionar

  • Criar o webhook "Oportunidade" nas configurações de API do SymplesDesk, apontando para <URL-do-serviço>/webhooks/oportunidade e colando no campo "Token de autenticação" o valor de SYMPLESDESK_OPORTUNIDADE_WEBHOOK_SECRET.
  • Preencher o mapa de etapas (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.
  • Definir a SDR dona dos cards de cada canal (symplesdesk_responsible_user_id): é por esse campo que cada uma filtra o quadro compartilhado para ver só os cards dela.
  • Definir o motivo de perda fixo de cada canal (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.

Cards de Consulta — o quadro da Concierge

gatilho: negócio de Consulta criado no ganho do lead, ou paciente com consulta aberta que volta a falar

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.

Quando o card aparece

  1. No ganho do lead: assim que o Pipedrive cria o negócio de Consulta, o card nasce — em poucos segundos, na primeira etapa do funil.
  2. Paciente que já tinha consulta aberta: o card nasce na hora em que ele manda mensagem, já na etapa em que o negócio está hoje. É assim que a fila antiga entra no quadro: aos poucos, conforme cada paciente volta a falar — sem despejar centenas de cards de uma vez.

O que muda em relação ao card de Leads

 Card de Leads (SDR)Card de Consulta (Concierge)
Dona do carda SDR do médicoa Concierge do médico
Nome do cardmontado pelo sistema: nome - siglacópia exata do nome do negócio: CONSULTA DE nome
Etapasas do funil de Leadsas do funil de Consulta
Renomear o cardcorrige o nome da pessoa no CRMnão propaga nada — ver abaixo

O que funciona igual

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.

Por que renomear não propaga. O card de Consulta copia o nome do negócio letra por letra — é assim que os dois lados ficam reconhecíveis como a mesma coisa. Se o sistema também tentasse "corrigir" nomes por aqui, ele empurraria o texto inteiro ("CONSULTA DE Maria") por cima do nome da paciente no CRM. Já aconteceu uma vez, em 06/08, e foi por isso que a regra virou: nome de card de Consulta nasce colado ao do negócio e ninguém mexe depois.
O nome do card é uma fotografia do momento do ganho. Se a paciente ainda estava cadastrada com o telefone quando o lead foi ganho, o card de Consulta nasce com o telefone — e não muda sozinho depois, mesmo que alguém corrija o cadastro. Para renomear, é na tela, dos dois lados.

O que precisa para funcionar

  • Definir a Concierge dona dos cards de cada canal (symplesdesk_concierge_user_id): é o campo que liga esta parte. Vazio, nenhum card de Consulta é criado — e o resto do sistema segue normal.
  • Preencher o mapa de etapas do funil de Consulta (integration_pipeline_steps com pipeline_type = 'consulta') — ver a seção Mapa das etapas.
  • Nada de webhook novo: a captura usa os mesmos que os fluxos 3 e 4 já usam.
A automação do Pipedrive continua sendo a dona do negócio. Quem cria o negócio de Consulta no ganho é ela, como sempre foi — o serviço só acompanha, criando o card e ligando os dois. Se um dia a automação criar dois negócios iguais, aparecerão dois cards: o quadro espelha o CRM, não o corrige.

Fechamento da conversa, 24h depois da perda

gatilho: verificação automática de hora em hora, aos :15

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.

Duas travas antes de fechar

  1. Se o paciente voltou — tem qualquer negócio aberto — a conversa segue viva. Perdeu um lead mas abriu outro é paciente ativo, não caso encerrado.
  2. Se existe conversa mais recente que a do lead perdido, o sistema não fecha: fechar a errada seria pior que não fechar.
Por que existe um teto por rodada. O sistema fecha no máximo um punhado de conversas por execução. Não é limitação técnica: é proteção do fornecedor, cuja API já desativou um webhook nosso sozinha depois de receber falhas em sequência. Com o teto, o que sobra é fechado na hora seguinte — nada se perde.

Quando a SDR fecha na mão

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.

O que precisa para funcionar

  • Marcar o evento "Atendimento finalizado" na configuração de webhook do SymplesDesk, apontando para <URL-do-serviço>/webhooks/atendimento-finalizado, com o valor de SYMPLESDESK_ATENDIMENTO_WEBHOOK_SECRET no campo "Token de autenticação".
  • ⚠️ A rota precisa estar no ar ANTES de criar o webhook: o fornecedor desativa sozinho uma configuração que aponta para endereço inexistente.
  • Opcional: CLOSE_LOST_CRON (horário) e CLOSE_LOST_MAX_PER_RUN (teto) — ver Ligar e desligar.

Mapa das etapas — Pipedrive ↔ quadro do SymplesDesk administrador

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.

Funil de Leads (SDR)

EtapaNo quadro (SymplesDesk)Dr. ThiagoDr. Augusto
Cliente Potencial524637
Em conversa502736
Aguardando Pagamento483039

Funil de Consulta (Concierge)

EtapaNo quadro (SymplesDesk)Dr. ThiagoDr. Augusto
Consulta Agendada574755
Reagendamento584956
Consulta Confirmada594857

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.

Cuidado ao ler números soltos. Os dois sistemas numeram por conta própria, e os números se repetem entre funis: 48 é "Aguardando Pagamento" no quadro e "Consulta Confirmada" no funil de Consulta do Dr. Thiago — dois lugares completamente diferentes. Por isso a tradução leva sempre três informações: o médico, o funil e o número. Nunca cite um número sem dizer de que lado e de que funil ele é.
Etapa fora do mapa não quebra nada — e não passa despercebida. Se alguém criar uma etapa nova em qualquer um dos lados sem cadastrar a tradução, o movimento é ignorado com registro. O sistema prefere não fazer nada a chutar a etapa errada; mas o card para de acompanhar aquele deal em silêncio até alguém completar o mapa.

Ligar e desligar cada parte administrador

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.

DesligarComoEfeito
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
Depois de mexer no banco, reinicie o serviço. A configuração dos canais é lida uma vez, na inicialização. Sem o restart, a mudança não vale — e o comportamento antigo continua, dando a impressão de que a chave não funcionou.

O que a API precisa para funcionar administrador

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.

1 · Variáveis de ambiente (.env)

Os valores reais nunca ficam no código nem nesta página — vivem só no .env do servidor.

VariávelObrigatória?Para quê
DATABASE_URLSimConexão com o Postgres (fonte da verdade)
PIPEDRIVE_BASE_URLSimDomínio da conta Pipedrive (teste ou produção)
PIPEDRIVE_API_TOKENSimToken da API do Pipedrive
PIPEDRIVE_WEBHOOK_BASIC_USER / _PASSSimUsuário/senha que o webhook do Pipedrive usa para se autenticar
SYMPLESDESK_TOKEN_T / _AFluxo 2Tokens da API de fechar conversa, um por canal/médico
SYMPLESDESK_WEBHOOK_TOKENFluxo 4Token que autentica o webhook de mensagem (sem ele a rota recusa tudo)
PIPEDRIVE_ACTIVITY_TYPE_WHATSAPP_KEYFluxo 4Chave do tipo de atividade "Whatsapp" na conta
PIPEDRIVE_PROCEDURE_FIELD_KEYProduçãoCampo personalizado "Procedimento" do deal (não existe na conta de teste)
SYMPLESDESK_OPORTUNIDADE_WEBHOOK_SECRETCardsToken que autentica o webhook de Oportunidade (sem ele a rota recusa tudo)
SYMPLESDESK_ATENDIMENTO_WEBHOOK_SECRETFechamentoToken do webhook "Atendimento finalizado"
PIPEDRIVE_SURGERY_DATE_FIELD_KEYNPSCampo "Data da cirurgia"; ausente = tarefas de NPS desligadas
CONVERSATION_SYNC_CRONOpcionalHorário do job da conversa (cron, fuso Brasília; padrão 0 19 * * * = 19h)
CLOSE_LOST_CRONOpcionalQuando checar conversas a fechar (padrão 15 * * * * = de hora em hora, aos :15)
CLOSE_LOST_MAX_PER_RUNOpcionalTeto de fechamentos por rodada (padrão 25; 0 desliga)
NPS_SYNC_CRONOpcionalHorário do job de NPS (padrão 0 7 * * * = 7h)
DISCORD_ERROR_WEBHOOK_URLOpcionalAlertas operacionais de erro
PORTOpcionalPorta HTTP (padrão 3000)

2 · Banco de dados

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.)

3 · Webhooks

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 criarEventoURL da APIAutenticação
SymplesDesk (2 canais)Mensagem criada/webhooks/symplesdesk-newmessageToken de autenticação
Pipedrive (Webhooks v2)deal: create + change/webhooks/pipedriveUsuário e senha (Basic Auth)
Pipedrive (Webhooks v2)person: change (sync de nome)/webhooks/pipedriveUsuário e senha (Basic Auth)
SymplesDesk (2 canais)Novo atendimento/webhooks/symplesdesk—
SymplesDesk (2 canais)Oportunidade (card)/webhooks/oportunidadeToken de autenticação
SymplesDesk (2 canais)Atendimento finalizado/webhooks/atendimento-finalizadoToken de autenticação
Regra de ouro: cada evento deve ter um destino só. Se algum desses eventos já dispara para outra automação, troque a URL pela da API em vez de criar um webhook em paralelo — dois destinos processando o mesmo evento duplicam deals e fecham conversas duas vezes.
Crie o webhook só depois que a rota estiver no ar. O SymplesDesk desativa sozinho uma configuração que recebe falhas seguidas — já aconteceu aqui, com um webhook apontado para uma URL que ainda não existia.

Como adicionar um novo canal / médico administrador

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.

Passo 1 · Inserir o canal em public.integration_channels

Troque 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);
CampoO que é / onde encontrar
doctor_codeSigla única do médico (1 letra, ex.: 'T'). Aparece no título do deal (telefone - SIGLA) e amarra todas as outras configs.
doctor_nameNome de exibição (ex.: 'Dr. Thiago Delgado').
symplesdesk_account_idID da conta do canal no SymplesDesk (vem no payload dos webhooks do canal).
symplesdesk_whatsapp_idID do canal de WhatsApp no SymplesDesk (ex.: '139'). É por ele que o Fluxo 1 descobre de qual médico é o lead.
symplesdesk_base_urlSempre 'https://serv2api.simplesdesk.com.br' (base da API externa).
symplesdesk_api_idID (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_nameNome 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_idID do usuário do Pipedrive que será o dono dos deals criados (Configurações → Usuários).
pipedrive_pipeline_id / pipedrive_stage_idID do funil de Leads do médico e da etapa de entrada (aparecem na URL do Pipedrive ao abrir o funil/etapa).
pipedrive_label_idID da etiqueta aplicada à pessoa (produção usa a etiqueta "WhatsApp"). Pode ser NULL se não houver.
is_activetrue para ligar o canal; false desliga sem apagar a linha.

Passo 2 · Mapear os funis em public.integration_pipelines

Trê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);

Passo 3 · Traduzir as etapas em public.integration_pipeline_steps

Sem 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.

Passo 4 · Guardar o token no ambiente

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>

Passo 5 · Criar os webhooks do canal novo

Passo 6 · Reiniciar o serviço

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.

Conferência rápida: mande uma mensagem de teste pro canal novo e confira o deal criado no funil certo, com o dono certo e o título telefone - SIGLA. Qualquer erro fica registrado em integration_events.

Endpoints

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.

EndpointAutenticaçãoO 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/pipedriveBasic AuthFluxos 2, 3 e 5-B: status, captura e movimentação de deals + sync de nome do contato (person)
POST /webhooks/symplesdesk-newmessageToken no headerFluxo 4: ingestão de cada mensagem
POST /webhooks/oportunidadeToken no headerCards: movimento, ganho, perda e renome feitos no quadro
POST /webhooks/atendimento-finalizadoToken no headerRegistra 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.

Colocando no ar — passo a passo administrador

Estado atual: os 4 fluxos estão completos, com verificação independente e teste ponta a ponta com mensagens e conversas reais (22/07/2026).
  1. Deploy do serviço com o .env completo → gera a URL pública fixa da API.
  2. Preparar o banco: rodar db/schema.sql + db/seed_prod.sql (limpo — os dados atuais são de teste).
  3. Backfill: importar o histórico de deals do Pipedrive para o banco (npm run backfill, com --dry-run antes para conferir os números).
  4. Criar o webhook "Mensagem criada" nos 2 canais do SymplesDesk com a URL da API (Fluxo 4).
  5. Criar o webhook de deals no Pipedrive e apontar o evento de novo atendimento dos 2 canais para a URL da API (Fluxos 1, 2 e 3) — lembrando a regra de ouro: um destino por evento.
  6. Validar com um atendimento real e acompanhar integration_events e os alertas do Discord.