Integrações e API

Guia técnico do Widget Contact Desk para integração com Spincare

Documentação completa para implantar o Chat Widget do Contact Desk em sistemas web, WebView, Android, iOS ou interfaces nativas integradas ao Spincare.

Exemplo visual do Contact Desk para Integrações e API

Resposta rápida

O Chat Widget do Contact Desk pode ser integrado de duas formas: usando o script oficial, quando a interface do Contact Desk será exibida dentro de uma página ou WebView; ou usando a API pública do widget, quando o Spincare quiser construir uma experiência própria no aplicativo Android/iOS. Em ambos os cenários, a integração usa uma widget key pública, valida origem autorizada, mantém credenciais administrativas fora do front-end e registra a conversa no Contact Desk para operação, auditoria, chatbot, atendimento humano, anexos, CSAT e criação de tickets.

Passo a passo

  1. Defina com o Contact Desk qual tenant, fila, categoria e chatbot serão usados no canal Spincare.
  2. Cadastre os domínios, hosts ou origens autorizadas nas configurações do widget.
  3. Escolha o modo de integração: script oficial para WebView ou API pública para interface nativa.
  4. Consulte o endpoint de configuração antes de iniciar a conversa.
  5. Crie ou recupere a conversa com um session_id persistente e exclusivo por usuário/dispositivo/contexto.
  6. Envie mensagens com client_message_id para evitar duplicidade em retries.
  7. Sincronize mensagens por SSE ou por consulta incremental usando after_id.
  8. Envie anexos somente quando allow_attachments estiver habilitado.
  9. Registre feedback CSAT quando habilitado e encerre a conversa ao finalizar o atendimento.

Objetivo desta documentação

Este guia foi escrito para equipes técnicas que vão substituir outro widget de atendimento pelo Contact Desk, especialmente em cenários onde o widget será embarcado dentro de um produto de terceiros, como o aplicativo Android/iOS do Spincare. A documentação descreve fluxo, responsabilidades, payloads, limites, segurança e roteiro de homologação.

  • Explicar o funcionamento do widget sem depender de acesso administrativo ao Contact Desk.
  • Diferenciar instalação por script, WebView e consumo direto da API pública.
  • Definir quais dados podem trafegar no navegador ou aplicativo.
  • Reduzir risco de mensagens duplicadas, perda de contexto ou vazamento de credenciais.

Quando usar script oficial, WebView ou API pública

A escolha depende do nível de controle visual e operacional desejado. O script oficial é mais rápido e mantém a experiência visual do Contact Desk. A API pública exige mais desenvolvimento do integrador, mas permite criar uma interface totalmente nativa dentro do aplicativo.

  • Script oficial: recomendado para sites e portais web onde o balão do Contact Desk pode aparecer no canto da tela.
  • WebView: recomendado quando o aplicativo móvel pode carregar uma página web controlada e autorizada.
  • API pública: recomendado quando o Spincare deseja construir a tela de chat nativa, usando componentes próprios de Android/iOS.
  • Não misture script e API nativa na mesma sessão sem alinhar a estratégia de session_id.

Pré-requisitos de configuração no Contact Desk

Antes da integração, um administrador do tenant deve publicar o widget e validar o canal de atendimento. O Spincare não precisa receber senha, token administrativo, chave SMTP, chave de banco, segredo de webhook ou credencial interna.

  • Widget ativo e publicado no tenant correto.
  • Widget key pública fornecida pelo administrador.
  • Fila padrão definida para receber as conversas.
  • Categoria padrão definida quando conversas virarem ticket.
  • Chatbot publicado, se o atendimento inicial for automatizado.
  • Domínios/origens autorizadas cadastradas.
  • Anexos e CSAT habilitados somente se fizerem parte do fluxo.

Dados que o Spincare deve receber

A integração deve receber somente dados públicos e operacionais. A widget key identifica o widget, mas não substitui a validação de origem e não concede acesso administrativo ao Contact Desk.

  • Ambiente de produção: https://app.contactdesk.com.br
  • Endpoint base: https://app.contactdesk.com.br/api/widget/{WIDGET_KEY}
  • Widget key pública do tenant.
  • Lista de origens/domínios autorizados.
  • Campos obrigatórios retornados pelo GET /config.
  • Limites de anexo e formato aceitos.
  • Contato técnico para homologação e suporte.

Estratégia de session_id

O session_id é o identificador público da conversa no contexto do widget. Ele precisa ser persistente para recuperar uma conversa em andamento, mas não deve ser compartilhado entre pessoas diferentes.

  • Use um session_id único por usuário, dispositivo e contexto de atendimento.
  • Em aplicativo nativo, armazene o session_id em storage seguro do app enquanto a conversa estiver ativa.
  • Em WebView, evite depender apenas de localStorage se o app limpar dados entre sessões.
  • Não use CPF, e-mail puro, ID interno de paciente ou número de prontuário como session_id.
  • Se precisar relacionar profissional ou paciente, envie contexto apenas após acordo técnico e com dados mínimos necessários.

Fluxo recomendado da conversa

O fluxo seguro começa com a consulta da configuração, passa pela criação ou retomada da conversa, envia mensagens com idempotência e acompanha respostas por stream ou consulta incremental.

  • 1. Consultar GET /config para saber disponibilidade, aparência, campos obrigatórios, anexos e CSAT.
  • 2. Criar ou retomar POST /conversation com session_id e dados exigidos.
  • 3. Enviar mensagem em POST /message com client_message_id único.
  • 4. Receber respostas por GET /stream ou GET /messages?after_id={ultimo_id}.
  • 5. Enviar anexos por POST /attachment quando permitido.
  • 6. Encerrar por POST /end quando o usuário finalizar.
  • 7. Enviar POST /feedback quando CSAT estiver habilitado.

Chatbot, atendimento humano e handoff

Quando um chatbot está associado ao widget, as mensagens iniciais, menus e opções exibidas ao visitante são controladas pelo fluxo publicado no módulo Chatbot. As mensagens configuradas na aba Mensagens do widget ficam preservadas para fallback humano, mas não duplicam o conteúdo do chatbot.

  • O chatbot pode iniciar a conversa, apresentar menus e direcionar para fila humana.
  • O atendimento humano assume quando o fluxo encaminha para operador ou fila.
  • A interface deve exibir claramente se o usuário está com bot ou humano.
  • Eventos de digitação podem ser exibidos para tornar a conversa mais natural.
  • O bot pode ter delay por bloco para reduzir respostas instantâneas artificiais.

Mensagens, paginação e atualização incremental

Conversas longas não devem carregar todo o histórico a cada atualização. A API suporta leitura incremental e paginação para reduzir tráfego, latência e carga no banco de dados.

  • Use after_id para buscar somente mensagens novas.
  • Use before_id para carregar mensagens antigas sob demanda.
  • Use limit entre 1 e 100 registros.
  • Guarde o last_id retornado pela API para a próxima sincronização.
  • Evite polling agressivo quando o stream SSE estiver ativo.

Idempotência no envio de mensagens

Aplicativos móveis podem reenviar uma requisição por instabilidade de rede, duplo clique ou retry automático. Para evitar mensagens duplicadas, envie client_message_id único em cada mensagem do usuário.

  • Gere um client_message_id por mensagem antes de chamar a API.
  • Reutilize o mesmo client_message_id em tentativas de retry da mesma mensagem.
  • Se a API identificar duplicidade, ela retorna duplicate=true com a mensagem já registrada.
  • Não reutilize o mesmo client_message_id para mensagens diferentes.

Anexos e imagens coladas

O widget permite anexos quando a configuração allow_attachments estiver habilitada. No script oficial, imagens coladas na caixa de mensagem podem ser tratadas como arquivo enviado pelo chat. Em integração nativa, o aplicativo deve enviar o arquivo pelo endpoint de attachment.

  • Tamanho padrão do widget público: até 10 MB por arquivo.
  • Tipos comuns aceitos: JPG, PNG, WEBP, GIF, PDF, TXT, CSV, ZIP, DOC, DOCX, XLS e XLSX.
  • O upload deve usar multipart/form-data.
  • Downloads devem usar a URL assinada retornada pela API ou pela mensagem serializada.
  • Não construa manualmente URLs de download.

Rate limit e simultaneidade

As rotas públicas possuem limitação por rota, widget, session_id e IP. Isso evita abuso sem bloquear indevidamente todos os usuários de uma mesma rede corporativa.

  • GET /config: até 180 chamadas por minuto por chave de controle.
  • POST /conversation: até 30 chamadas por minuto.
  • POST /message: até 45 mensagens por minuto.
  • POST /attachment: até 12 uploads por minuto.
  • GET /messages: até 180 chamadas por minuto.
  • GET /stream: até 40 streams por minuto.
  • POST /end e POST /feedback: até 30 chamadas por minuto.

Eventos e observabilidade

Para integração crítica, os times devem conseguir investigar falhas sem acessar dados sensíveis desnecessários. O Contact Desk registra eventos operacionais do widget com hashes de identificadores e contexto técnico suficiente para diagnóstico.

  • Origem negada.
  • Conversa iniciada ou retomada.
  • Mensagem recebida.
  • Mensagem duplicada por client_message_id.
  • Anexo enviado.
  • Falha de chatbot ou transferência.
  • Rate limit aplicado.
  • Conversa encerrada e CSAT recebido.

Recomendação específica para Android e iOS do Spincare

Se o objetivo for substituir o widget atual rapidamente, a WebView com script oficial reduz o prazo de implantação. Se o objetivo for uma experiência totalmente integrada ao aplicativo, a API pública é o caminho mais limpo, desde que o Spincare implemente a interface e siga o contrato de sessão, idempotência e paginação.

  • Para primeira versão: WebView ou página web autorizada pode acelerar a homologação.
  • Para experiência definitiva: construir UI nativa consumindo a API pública.
  • Nunca embutir token administrativo do Contact Desk no aplicativo.
  • Evitar armazenar dados sensíveis do paciente no histórico local do app.
  • Usar logs com correlation_id interno do Spincare para cruzar chamados sem expor dados pessoais.

Checklist de homologação antes de publicar

Antes de liberar para usuários reais, valide o fluxo completo em Android, iOS, navegador, rede corporativa, 4G/5G e cenários de instabilidade.

  • Widget carrega apenas em origem autorizada.
  • Origem não autorizada retorna 403.
  • Conversa inicia com campos obrigatórios corretos.
  • Chatbot responde o fluxo publicado.
  • Handoff para humano funciona.
  • Indicador de digitação aparece sem travar a conversa.
  • Mensagem com retry não duplica.
  • Busca incremental por after_id funciona.
  • Upload de imagem, PDF e documento funciona.
  • Download de anexo usa URL assinada.
  • CSAT aparece somente quando habilitado.
  • Encerramento muda status corretamente.
  • Logs permitem rastrear falhas sem expor credenciais.

Endpoints documentados

Método Rota Autenticação Uso
GET /api/widget/{widgetKey}/config Widget key pública + origem autorizada Retorna status, aparência, chatbot, anexos, CSAT, departamentos e campos obrigatórios.
POST /api/widget/{widgetKey}/conversation Widget key pública + origem autorizada Inicia ou recupera conversa ativa usando session_id.
POST /api/widget/{widgetKey}/conversation/{conversationId}/message session_id da conversa Envia mensagem do visitante com client_message_id opcional para idempotência.
GET /api/widget/{widgetKey}/conversation/{conversationId}/messages?session_id={sessionId}&after_id={lastId}&limit=80 session_id da conversa Lista mensagens com paginação incremental; suporta after_id, before_id e limit.
GET /api/widget/{widgetKey}/conversation/{conversationId}/stream?session_id={sessionId}&last_id={lastId} session_id da conversa Abre stream SSE para receber mensagens e estados em tempo quase real.
POST /api/widget/{widgetKey}/conversation/{conversationId}/attachment session_id da conversa Envia anexo via multipart/form-data quando anexos estiverem habilitados.
GET /api/widget/{widgetKey}/conversation/{conversationId}/attachments/{messageId} URL assinada temporária Baixa anexo privado da conversa usando assinatura válida.
POST /api/widget/{widgetKey}/conversation/{conversationId}/end session_id da conversa Encerra conversa ativa ou em espera.
POST /api/widget/{widgetKey}/conversation/{conversationId}/feedback session_id da conversa Registra avaliação CSAT quando habilitada no widget.

Exemplos seguros

Instalação por script oficial
<!-- Contact Desk Chat Widget -->
<script>
  (function(w,d,s,o,f,js,fjs){
    w['ContactDeskWidget']=o;
    w[o]=w[o]||function(){(w[o].q=w[o].q||[]).push(arguments)};
    js=d.createElement(s);
    fjs=d.getElementsByTagName(s)[0];
    js.id=o;
    js.src=f;
    js.async=1;
    fjs.parentNode.insertBefore(js,fjs);
  }(window,document,'script','cdw','https://app.contactdesk.com.br/widget/widget.js'));

  cdw('init', '{WIDGET_KEY_PUBLICA}');
</script>
<!-- End Contact Desk Chat Widget -->
Consultar configuração antes de abrir o chat
const widgetKey = '{WIDGET_KEY_PUBLICA}';
const baseUrl = `https://app.contactdesk.com.br/api/widget/${widgetKey}`;

const response = await fetch(`${baseUrl}/config`, {
  headers: { Accept: 'application/json' },
});

if (!response.ok) {
  throw new Error(`Falha ao carregar widget: ${response.status}`);
}

const config = await response.json();

const requiresName = Boolean(config.requirements?.name);
const requiresEmail = Boolean(config.requirements?.email);
const allowAttachments = Boolean(config.allow_attachments);
Iniciar ou recuperar conversa
const sessionId = await getOrCreateSecureSessionId();

const response = await fetch(`${baseUrl}/conversation`, {
  method: 'POST',
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    session_id: sessionId,
    visitor_name: 'Nome do visitante',
    visitor_email: 'visitante@empresa.com',
    visitor_phone: '(00) 00000-0000',
    initial_message: 'Olá, preciso de atendimento.',
  }),
});

const data = await response.json();
const conversationId = data.conversation.id;
const lastId = data.last_id ?? 0;
Enviar mensagem com idempotência
const clientMessageId = crypto.randomUUID();

const response = await fetch(`${baseUrl}/conversation/${conversationId}/message`, {
  method: 'POST',
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    session_id: sessionId,
    sender_name: 'Visitante',
    message: 'Preciso acompanhar minha solicitação.',
    client_message_id: clientMessageId,
  }),
});

const data = await response.json();

if (data.duplicate === true) {
  renderMessage(data.message);
}
Buscar somente mensagens novas
async function fetchNewMessages(lastId) {
  const url = new URL(`${baseUrl}/conversation/${conversationId}/messages`);
  url.searchParams.set('session_id', sessionId);
  url.searchParams.set('after_id', String(lastId));
  url.searchParams.set('limit', '80');

  const response = await fetch(url, {
    headers: { Accept: 'application/json' },
  });

  const data = await response.json();

  return {
    messages: data.messages ?? [],
    lastId: data.last_id ?? lastId,
    typing: data.typing ?? null,
  };
}
Enviar anexo por API pública
const formData = new FormData();
formData.append('session_id', sessionId);
formData.append('file', selectedFile);

const response = await fetch(`${baseUrl}/conversation/${conversationId}/attachment`, {
  method: 'POST',
  body: formData,
});

if (response.status === 403) {
  throw new Error('Anexos não estão habilitados para este widget.');
}

const data = await response.json();
renderMessage(data.message);

Cuidados de segurança

  • A widget key é pública, mas deve funcionar apenas em origens autorizadas no Contact Desk.
  • Nunca envie token administrativo, senha SMTP/IMAP, chave de banco, segredo de webhook ou credencial interna para o aplicativo ou navegador.
  • Use HTTPS em todos os ambientes, incluindo páginas WebView.
  • Não use CPF, e-mail ou ID de paciente como session_id.
  • Evite enviar dados clínicos ou dados sensíveis em metadados livres sem necessidade operacional e base legal.
  • Implemente retry com client_message_id para não duplicar mensagens.
  • Use after_id e limit para não recarregar conversas longas a cada atualização.
  • Baixe anexos apenas por URLs assinadas retornadas pela API.
  • Registre logs no Spincare com identificadores internos ou hashes, não com credenciais ou dados sensíveis em texto aberto.
  • Valide limites de anexo no aplicativo antes do upload para melhorar experiência do usuário.

Boas práticas

  • Revise permissões antes de liberar recursos para novos usuários.
  • Teste mudanças em um fluxo controlado antes de aplicar em toda a operação.
  • Use nomes claros para filas, categorias, automações e respostas prontas.
  • Consulte logs e auditoria quando uma ação precisar de rastreabilidade.

Perguntas frequentes

A widget key é uma senha?

Não. A widget key é um identificador público do widget. A segurança depende da validação de origem, da sessão da conversa, dos limites de uso e das URLs assinadas para anexos. Mesmo assim, ela não deve ser divulgada fora do contexto de instalação.

O Spincare precisa receber token da API do Contact Desk?

Para o widget público, não. O widget usa endpoints próprios com widget key e origem autorizada. Tokens administrativos só são necessários para integrações privadas de backend e não devem ser usados no aplicativo móvel.

O widget substitui o módulo Chatbot?

Não. O widget é o canal de entrada. Quando há chatbot vinculado, o fluxo publicado no módulo Chatbot controla saudação, menus, atrasos e encaminhamento para humano.

É possível criar uma interface nativa no Android/iOS?

Sim. Nesse caso, o app consome a API pública do widget e implementa a interface de conversa. É obrigatório respeitar session_id, client_message_id, paginação incremental, rate limit, anexos e encerramento.

Como o Contact Desk evita mensagens duplicadas?

O endpoint de mensagem aceita client_message_id. Quando o aplicativo reenviar a mesma mensagem por instabilidade, deve reutilizar o mesmo identificador. A API pode retornar duplicate=true quando a mensagem já estiver registrada.

Como anexos são protegidos?

Os arquivos são gravados no armazenamento privado do Contact Desk e o download público usa URL assinada temporária. O integrador não deve montar links manualmente nem expor caminhos internos de storage.

Quando acionar o administrador?

Acione um administrador quando a configuração envolver credenciais, permissões, integrações, filas compartilhadas, políticas de SLA, automações globais ou dados sensíveis.