Widget Marco · Documentação técnica
O widget do Marco é instalado de três formas diferentes, conforme o lugar onde a sua aplicação vive e onde os dados da sessão precisam permanecer. Este guia cobre as três. Escolha o seu fluxo e siga apenas essa coluna: cada uma é autocontida.
Passo zero
A pergunta que decide é onde a sessão é criada e o que o navegador recebe. Todo o resto decorre disso.
Fluxo A
Sites sem autenticação. Duas tags HTML e nada mais: a sessão começa sozinha. A confiança se apoia na origem do domínio e no appId.
Fluxo B
Seu frontend já gerencia usuários autenticados. Você inicia a sessão com init() e uma identidade assinada com HMAC pelo seu backend. Sem endpoint próprio.
Fluxo C
O contexto clínico nunca toca o navegador. Seu backend cria a sessão pela API e o frontend recebe apenas um token opaco de uso único.
| Página pública | Página privada | Servidor a servidor | |
|---|---|---|---|
| Quem cria a sessão | O script, automaticamente | Seu frontend, via init() |
Seu backend, pela API |
| O que o navegador recebe | Apenas o appId |
user_id, assinatura e metadata |
Apenas um token opaco |
| Autenticação | Origem + appId |
Assinatura HMAC-SHA256 | Bearer client_secret |
| Incorporação | Script + elemento <marco> |
Script + API JavaScript | <iframe> com o token na URL |
| Backend necessário | Não | Só para assinar | Sim |
| Contexto clínico | Não se aplica | Passa pelo navegador, assinado | Nunca sai do servidor |
| Renderização inline | Não, sempre flutuante | Sim, via component_id |
Sim, você controla o iframe |
| Sessões retomáveis | Sim, via localStorage |
Sim, por user_id/external_id |
Não, por design |
Os estados se escrevem igual nos três fluxos
Os três publicam os nomes de estado em maiúsculo (READY, SESSION_CREATED, STARTED), então um handler escrito para um fluxo pode ser reaproveitado em outro sem normalizar. O que muda entre fluxos é quais estados existem, não como são escritos.
Guia por fluxo
A sessão é identificada por origem (domínio) e appId, ambos configurados com o Marco de antemão. O widget é carregado com uma única tag de script e começa sozinho, sem chamar nenhuma API JavaScript.
appId.Adicione estas duas tags à sua página, uma no head e outra no body. É só isso: nenhuma outra chamada é necessária.
<html>
<head>
<script
src="https://production.deep-talk.ai/widget/widget.js"
defer>
</script>
</head>
<body>
<marco
id="my-marco-widget"
api-base-url="https://production.deep-talk.ai/widget"
app-id="YOUR_APP_ID">
</marco>
</body>
</html>
Substitua YOUR_APP_ID pelo identificador que o Marco fornece. Carregar o script é suficiente: o widget aparece automaticamente como bolha flutuante.
A configuração vive no backend do Marco
Cores da marca, comportamento e demais ajustes são configurados uma vez com o seu contato no Marco e ficam associados ao seu appId. Uma mudança é aplicada do lado do Marco e aparece automaticamente na sua página, sem novo deploy. A URL do script também é estática: as correções do produto chegam sozinhas.
A sessão é criada assim que o widget termina de carregar. Cada navegador mantém uma única conversa contínua, identificada pelo seu próprio session_id.
O widget gera um identificador de sessão aleatório na primeira vez que carrega em um navegador e o guarda no localStorage. Ele não está vinculado a nenhuma identidade real: serve apenas para retomar a conversa naquele mesmo navegador e como identificador de correlação para eventos e auditoria.
Recarregar a página, navegar e voltar, ou fechar e reabrir a aba ou o navegador (mesmo perfil): em todos esses casos o widget encontra o identificador guardado e, se a sessão ainda estiver dentro do seu tempo limite de inatividade, retoma a conversa e publica SESSION_RELOADED em vez de SESSION_CREATED.
Uma sessão sem atividade além do limite configurado é encerrada automaticamente. O carregamento seguinte do widget naquele navegador já não encontra sessão para retomar e publica SESSION_CREATED.
Por padrão a sessão só termina por inatividade ou por ação do usuário dentro do widget. Se você precisar encerrá-la de forma programática, use MarcoWidget.finish().
Para a maioria das integrações, a seção anterior é tudo o que é preciso. Se a sua aplicação precisa reagir a eventos ou encerrar a sessão explicitamente, o script expõe um namespace global opcional, MarcoWidget, em window.
MarcoWidget.on('READY', function () {
console.log('Widget Marco pronto');
});
MarcoWidget.on('SESSION_CREATED', function (msg) {
console.log('Nova sessão:', msg.session_id);
});
// Encerra a sessão e limpa o identificador do localStorage.
// O próximo carregamento inicia uma sessão nova.
MarcoWidget.finish();
MarcoWidget.on() inscreve um callback nos eventos de estado e cuida da verificação de origem para você. Neste fluxo a sessão já está ativa quando o script carrega, então MarcoWidget serve apenas para ouvir eventos e, opcionalmente, encerrar a sessão.
O widget publica o estado da sessão com postMessage, emitido do seu iframe para a janela que o contém. MarcoWidget.on() é um wrapper sobre esse mesmo fluxo.
| Estado | Publicado quando |
|---|---|
READY |
O script terminou de carregar. A sessão ainda está sendo criada. |
SESSION_CREATED |
Uma nova sessão foi criada para este navegador. |
SESSION_RELOADED |
Uma sessão ativa existente foi encontrada e retomada neste navegador. |
STARTED |
O visitante enviou uma consulta e a interação está em andamento. |
RESPONSE_CREATED |
O agente produziu uma nova resposta dentro da sessão ativa. |
COMPLETED |
A sessão terminou normalmente. Inclui created_at e finished_at. |
CANCELLED |
Terminou antes da hora: ação do visitante, finish() ou inatividade. |
ERROR |
A sessão ou a chamada em andamento terminou por uma falha. |
Estados terminais
COMPLETED, CANCELLED e ERROR são terminais. Nenhum deles pode ser seguido de mais atividade na mesma sessão, e o próximo carregamento do widget publicará SESSION_CREATED.
READY, STARTED, CANCELLED e ERROR compartilham esta estrutura base. session_id está ausente em READY e presente a partir de SESSION_CREATED ou SESSION_RELOADED.
{
"source": "marco-widget",
"version": 1,
"type": "STATE_CHANGE",
"state": "READY",
"session_id": "sess_9f2a1c",
"timestamp": "2026-08-20T14:32:04Z"
}
COMPLETED acrescenta dois campos sobre a base: created_at e finished_at.
Os erros chegam como um evento de estado ERROR, na mesma estrutura acima, com os campos code e message.
| Código | Causa comum |
|---|---|
unauthorized_origin |
A requisição veio de uma origem fora da lista permitida para este appId. |
invalid_app_id |
O appId não corresponde a nenhuma conta configurada, ou foi omitido. |
rate_limited |
O limite de requisições para este appId ou endereço IP foi excedido. |
network_error |
A requisição ao backend do Marco falhou: conectividade ou timeout. |
A origem é o sinal de confiança primário deste fluxo. Cada carregamento do widget é comparado com os domínios autorizados para o appId informado, e toda requisição de uma origem fora dessa lista é rejeitada com unauthorized_origin. O Marco também aplica limites de taxa por appId e por endereço IP.
O script e suas chamadas são servidos apenas por HTTPS, e a sua aplicação deve ser servida por HTTPS em todos os ambientes. O widget roda dentro de um iframe em sandbox com permissões mínimas, gerenciado automaticamente pelo script carregador.
Se a sua aplicação aplica uma Content-Security-Policy, autorize o domínio do widget nas três diretivas que governam o carregamento do script, a incorporação do iframe e as chamadas de rede do widget.
Content-Security-Policy:
script-src https://production.deep-talk.ai;
frame-src https://production.deep-talk.ai;
connect-src 'self' https://production.deep-talk.ai;
Seu frontend inicia e gerencia a sessão diretamente, chamando o MarcoWidget com uma identidade de usuário assinada pelo seu backend. O navegador recebe a assinatura, nunca o segredo.
component_id.O contexto clínico passa pelo navegador
O objeto metadata viaja pelo navegador a caminho do Marco, assinado junto com a identidade do usuário. Evite incluir informação diretamente identificável: nome, data de nascimento ou número de prontuário. Se o contexto não pode sair do servidor, use o fluxo servidor a servidor.
Esta tag carrega o shell do widget. A sessão é iniciada separadamente, com a chamada a init. Nenhuma interface visível é renderizada até lá.
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://production.deep-talk.ai/widget/widget.js"
defer onload="initializeMarco()"></script>
</head>
<body>
<script>
const signature = '<SIGNATURE_FROM_BACKEND>';
const timestamp = '<TIMESTAMP_FROM_BACKEND>';
function initializeMarco() {
window.$MarcoWidget.init({
apiBaseUrl: 'https://production.deep-talk.ai/widget',
appId: 'marco-demo',
floating: true,
user_id: '323232',
created_at: timestamp,
signature: signature,
});
}
</script>
</body>
</html>
init(), normalmente no mesmo lugar onde você já trata a autenticação: um handler de login bem-sucedido, um efeito de React, um callback de auth.init() novamente com o novo external_id.finish(), que encerra a sessão ativa e limpa os dados armazenados localmente.Seu backend calcula localmente uma assinatura HMAC-SHA256 com um segredo compartilhado emitido pelo Marco, e seu frontend a envia junto com o user_id. Trate o segredo como uma chave de API do lado do servidor: leia-o do seu gerenciador de segredos e nunca o envie ao frontend, a um cliente mobile ou a terceiros.
// Fórmula base
signature = HMAC-SHA256(shared_secret, user_id)
// Com proteção contra repetição (recomendado em produção)
signature = HMAC-SHA256(shared_secret, `${user_id}:${created_at}`)
// Node.js
const crypto = require('crypto');
const signature = crypto
.createHmac('sha256', sharedSecret) // segredo, como UTF-8
.update(userId, 'utf8') // user_id, como UTF-8
.digest('hex'); // hexadecimal minúsculo
Tanto o segredo quanto o user_id devem ser codificados em UTF-8 antes de assinar, e a assinatura resultante deve ser representada em hexadecimal minúsculo. Uma assinatura que não respeite essa codificação exata falha na verificação mesmo que o segredo esteja correto.
Por padrão a assinatura só prova que o user_id foi assinado por alguém de posse do seu segredo, não quando. Passar um created_at opcional fecha essa brecha. É um timestamp UTC em formato ISO 8601 e, quando incluído, deve fazer parte tanto do payload quanto da assinatura. O Marco rejeita chamadas cujo created_at esteja a mais de cinco minutos da hora atual, em qualquer direção.
Manejo da chave
O Marco gera o segredo compartilhado e o entrega a você. Guarde-o apenas no seu backend: ele não deve aparecer em código de frontend, armazenamento do navegador nem em qualquer payload de requisição. Cada ambiente tem o seu próprio segredo. Se suspeitar que algum foi exposto, entre em contato com o Marco para rotacioná-lo imediatamente.
| Campo | Usado em | Obrigatório | Descrição |
|---|---|---|---|
user_id |
init, finish |
Sim | Seu próprio identificador para o usuário final. |
signature |
init, finish |
Sim | Assinatura HMAC-SHA256 do user_id, e do created_at quando incluído. |
created_at |
init, finish |
Não | Timestamp UTC ISO 8601 para proteção contra repetição. Recomendado em produção. |
external_id |
init |
Não | Identifica o contexto aberto, por exemplo o registro do paciente. Omita para uma única conversa global por user_id; forneça de forma consistente se um mesmo usuário deve ver uma conversa diferente por contexto. |
component_id |
init |
Não | Id do elemento DOM onde o Marco deve renderizar inline. Se omitido, renderiza flutuante. |
metadata |
init |
Não | JSON de formato livre passado à sessão do agente como contexto. Não há esquema fixo. |
{
"specialty": "oncology",
"diagnosis": "HER2-positive breast cancer, stage III",
"current_treatment": "trastuzumab + pertuzumab, cycle 4 of 6",
"disease_state": "partial response, stable since last imaging",
"ecog_status": "1",
"line_of_therapy": "second line"
}
Prefira chaves descritivas e autoexplicativas a abreviações. O metadata é interpretado pelo agente ao iniciar a sessão, então a clareza aqui melhora diretamente a qualidade das respostas. O exemplo é orientativo: também é possível trabalhar com texto mais extenso ou resumos, se isso representar melhor os seus dados.
Aplicam-se dois tempos de vida independentes: a janela de validade da assinatura e a sessão em si.
Quando created_at é incluído, a assinatura é válida por cinco minutos a partir da emissão. Ela é recalculada, ou reaproveitada dentro da sua janela, a cada chamada a init ou finish.
Corre por um tempo de vida configurado de forma independente uma vez iniciada. Estende-se com a atividade e termina por encerramento explícito, por inatividade ou ao expirar esse tempo de vida.
As sessões podem ser retomadas
O widget guarda um identificador da sessão ativa no localStorage, por user_id e external_id. Quando a sua aplicação chama init() de novo com o mesmo par, o Marco procura uma sessão ativa. Se existir e ainda estiver dentro do seu tempo limite de inatividade, a conversa é restaurada e SESSION_RELOADED é publicado em vez de SESSION_CREATED.
Para apontar o widget a um paciente ou registro diferente, chame init() com o novo external_id. Isso não atualiza a sessão aberta: é uma sessão distinta, nova ou retomada se aquele contexto já tinha uma ativa.
As três ações se comportam igual: o iframe é descartado, mas o identificador persiste no localStorage. A próxima chamada a init() com o mesmo user_id e external_id tenta retomar automaticamente, como efeito dessa mesma chamada.
MarcoWidget.finish() encerra a sessão imediatamente e limpa o identificador guardado. Depois de finish(), uma chamada a init() com o mesmo par inicia uma sessão nova.
O widget publica o estado com postMessage do seu iframe para a janela que o contém. O script carregador também expõe MarcoWidget.on(evento, callback), um wrapper sobre esse mesmo fluxo que cuida da verificação de origem para você.
MarcoWidget.on('READY', function () {
console.log('Widget Marco pronto para receber init()');
});
MarcoWidget.on('SESSION_CREATED', function (msg) {
console.log('Nova sessão:', msg.session_id);
});
MarcoWidget.on('ERROR', function (err) {
console.error('Erro do widget Marco:', err.code, err.message);
});
| Estado | Publicado quando |
|---|---|
READY |
O script terminou de carregar e está pronto para receber init(). Ainda não existe sessão. |
SESSION_CREATED |
init() criou uma nova sessão para o user_id/external_id informado. |
SESSION_RELOADED |
init() encontrou e retomou uma sessão ativa para o mesmo par. |
STARTED |
O usuário enviou uma consulta e a interação está em andamento. |
RESPONSE_CREATED |
O agente produziu uma nova resposta dentro da sessão ativa. |
COMPLETED |
Terminou normalmente. Inclui created_at, finished_at e, opcionalmente, metadata. |
CANCELLED |
Terminou antes da hora: ação do usuário, finish() ou inatividade. |
ERROR |
A sessão ou a chamada em andamento terminou por uma falha. |
session_id e external_id estão ausentes em READY e presentes a partir de SESSION_CREATED ou SESSION_RELOADED.
{
"source": "marco-widget",
"version": 1,
"type": "STATE_CHANGE",
"state": "SESSION_RELOADED",
"session_id": "sess_9f2a1c",
"external_id": "patient_789",
"timestamp": "2026-08-20T14:32:04Z"
}
A assinatura é calculada localmente no seu backend, sem ida e volta ao Marco. Uma assinatura inválida ou vencida é descoberta quando o widget tenta usá-la, e chega como um evento de estado ERROR.
| Código | Causa comum |
|---|---|
invalid_signature |
A assinatura não corresponde ao valor que o Marco recalcula para o payload dado. |
expired_timestamp |
created_at foi incluído mas cai fora da janela de cinco minutos. |
missing_required_field |
Faltou user_id ou signature na chamada. |
session_not_found |
Não há sessão ativa correspondente, por exemplo ao chamar finish() sobre uma já encerrada. |
component_not_found |
component_id foi incluído mas não corresponde a nenhum elemento presente no DOM. |
unauthorized_origin |
A requisição veio de uma origem fora da sua lista de permitidos. |
network_error |
A requisição ao backend do Marco falhou: conectividade ou timeout. |
O user_id nunca é confiado por si só. O Marco recalcula a assinatura de forma independente e a compara antes de iniciar ou continuar qualquer sessão. Cada chamada a init ou finish também é comparada com os domínios autorizados para a sua conta, e rejeitada com unauthorized_origin se a origem não estiver na lista, mesmo que a assinatura seja válida.
Uma assinatura válida confirma que o seu backend, de posse do segredo, assinou aquele user_id. Verificar se aquele usuário deve ter acesso ao registro descrito em metadata é responsabilidade da sua própria aplicação, antes de chamar init(). A decisão de acesso é tomada nesse momento; a assinatura apenas a transmite.
Content-Security-Policy:
script-src https://production.deep-talk.ai; // script de inicialização
frame-src https://production.deep-talk.ai; // iframe em sandbox
connect-src 'self' https://production.deep-talk.ai; // chamadas do widget
O script e suas chamadas são servidos apenas por HTTPS. O widget roda em um iframe em sandbox com permissões mínimas, e usa localStorage para a retomada; esse cache é limpo ao chamar finish().
Seu backend cria a sessão diretamente com o backend do Marco. Seu frontend nunca recebe dados do escopo da sessão, apenas um token opaco, de uso único e de curta duração para iniciar o chat.
client_secret, de servidor a servidor.<iframe> que você controla.Seu backend chama o endpoint de sessão do Marco por HTTPS. A credencial é apresentada como bearer token e é de longa duração: é emitida uma vez por cliente, não por sessão.
POST /launch_token
Host: production.deep-talk.ai
Authorization: Bearer <client_secret>
Content-Type: application/json
{
"app_id": "oncology-widget",
"user_id": "doctor_123",
"metadata": {
"specialty": "oncology",
"diagnosis": "HER2-positive breast cancer, stage III",
"current_treatment": "trastuzumab + pertuzumab, cycle 4 of 6"
}
}
| Campo | Obrigatório | Descrição |
|---|---|---|
app_id |
Sim | Identifica qual configuração de app ou agente do Marco esta sessão usa. Atribuído pelo Marco durante a integração inicial. |
user_id |
Não | Seu próprio identificador para o usuário final. Use um ID interno, não um nome nem um e-mail. Útil para estatísticas de uso, mas pode ser omitido. |
metadata |
Não | JSON de formato livre que descreve o contexto da sessão. Usado apenas do lado do servidor. |
{
"launch_token": "lt_7Kx0mQe2vB…",
"session_id": "sess_9f2a1c",
"expires_at": "2026-08-20T14:37:00Z"
}
launch_token é opaco, de uso único e de curta duração, e não contém nenhum dado subjacente da sessão. Guarde o session_id para correlacioná-lo com os seus próprios registros de auditoria; você não precisará dele de novo para interagir com o widget.
Seu frontend exibe um iframe apontado para a URL de sessão com o launch_token anexado. O token na URL é a única entrada que este fluxo exige do seu frontend. Aplique codificação de URL ao valor, já que ele pode conter caracteres que exigem codificação percentual.
<iframe
src="https://production.deep-talk.ai/chat?launch_token=lt_7Kx0mQe2vB%2E…"
sandbox="allow-scripts allow-same-origin allow-forms"
allow="clipboard-write"
referrerpolicy="no-referrer"
></iframe>
| Valor do sandbox | Concede |
|---|---|
allow-scripts |
Execução de JavaScript dentro do iframe. Obrigatório: o widget não funciona sem ele. |
allow-same-origin |
Trata o iframe como sua origem real em vez de uma origem nula isolada. Obrigatório para que use o seu próprio armazenamento e para que as verificações de origem funcionem. |
allow-forms |
Envio de formulários. Obrigatório para a redação de mensagens. |
Não conceda permissões a mais
Omita allow-popups, allow-top-navigation, allow-modals e allow-downloads a menos que um recurso específico os exija. allow-top-navigation em particular permitiria que o iframe redirecionasse a sua página inteira.
Quando o iframe carrega, o Marco valida o token antes de exibir qualquer conteúdo.
launch_token em relação à sessão criada.session_id, estado de pronto e configuração de exibição, gerados a partir do metadata já consumido no servidor.READY ao contêiner.O metadata permanece no servidor
O metadata é escrito uma única vez, de servidor a servidor, e consumido ali para configurar a sessão. Se a sua integração exige que o frontend exiba esse contexto junto ao widget, renderize-o a partir do seu próprio backend.
Além disso, READY indica apenas que o widget está interativo: o metadata da sessão é processado somente quando o usuário realiza uma ação explícita para começar.
Aplicam-se dois tempos de vida independentes. O launch_token dura cinco minutos e é de uso único: existe apenas para levar o iframe de "tem uma referência" a "sessão em execução". A sessão corre por um tempo de vida mais longo, configurado por integração, estende-se com a atividade, e termina por encerramento explícito, inatividade ou expiração.
As sessões não podem ser retomadas
Não existe nenhum mecanismo para retomar uma sessão depois que o seu launch_token foi consumido ou expirou. É intencional: o uso único é o que torna o token resistente a repetição. A única opção após o fim de uma sessão, por qualquer motivo, é criar uma nova com um novo launch_token.
Fechar a aba, recarregar a página ou desmontar o iframe ao navegar produzem o mesmo resultado: o iframe é descartado e a sessão termina com ele. O token já foi consumido assim que o carregamento anterior teve sucesso, então não pode ser reutilizado. Seu frontend deve solicitar uma nova sessão ao seu backend, que emite um novo token com o contexto atualizado. Se, em vez disso, o seu contêiner mantém o iframe montado durante a navegação, a sessão continua ativa até o seu tempo limite de inatividade ou o encerramento explícito.
O metadata é fixado ao criar a sessão e não pode ser atualizado em uma sessão em execução. Para apontar o widget a um paciente ou registro diferente, solicite uma nova sessão com o novo contexto.
O widget publica o estado diretamente à janela que o contém com postMessage. Sua página escuta com um listener padrão. Verifique sempre event.origin antes de ler event.data, nunca o compare com '*', e nunca confie em uma mensagem que não inclua source: 'marco-widget'.
const MARCO_ORIGIN = 'https://production.deep-talk.ai';
window.addEventListener('message', (event) => {
if (event.origin !== MARCO_ORIGIN) return;
const msg = event.data;
if (msg?.source !== 'marco-widget') return;
switch (msg.state) {
case 'READY': /* o widget está interativo */ break;
case 'STARTED': /* o usuário enviou uma consulta */ break;
case 'COMPLETED': /* sessão finalizada */ break;
case 'CANCELLED': /* cancelada por usuário ou app */ break;
case 'ERROR': /* ver a seção de erros */ break;
}
});
| Estado | Publicado quando |
|---|---|
READY |
O widget validou o token, iniciou a sessão e está interativo. |
STARTED |
O usuário enviou uma consulta e a interação está em andamento. |
COMPLETED |
Terminou normalmente. Inclui created_at, finished_at e, opcionalmente, metadata. |
CANCELLED |
Terminou antes da hora, por ação do usuário ou da sua aplicação. |
ERROR |
A sessão terminou devido a uma falha. |
Seu contêiner pode enviar comandos da mesma forma, dirigidos ao contentWindow do iframe. Passe sempre a origem de destino exata como segundo argumento, nunca '*', para que o navegador se recuse a entregar a mensagem se o iframe tiver navegado para outro lugar de forma imprevista.
const iframeWindow = document.querySelector('iframe').contentWindow;
iframeWindow.postMessage({
source: 'marco-host',
version: 1,
type: 'COMMAND',
action: 'CLOSE', // única ação suportada atualmente
session_id: 'sess_9f2a1c'
}, MARCO_ORIGIN);
Ao recebê-lo, o widget verifica a origem contra as autorizadas para a sua conta antes de executá-lo. Um comando vindo de uma origem não registrada é descartado em silêncio: o widget não age nem emite ERROR, já que uma origem não reconhecida pode não ser um emissor legítimo. Um CLOSE válido encerra a sessão imediatamente e o widget emite CANCELLED de volta, para que o seu contêiner tenha uma confirmação limpa e correlacionável.
{
"source": "marco-widget",
"version": 1,
"type": "STATE_CHANGE",
"state": "COMPLETED",
"session_id": "sess_9f2a1c",
"created_at": "2026-08-20T14:32:00Z",
"finished_at": "2026-08-20T14:36:12Z",
"metadata": {
"query_summary": "Asked about second-line options for HER2-positive…",
"final_synthesis": "Reviewed second-line options for HER2-positive…"
}
}
O objeto metadata é específico de cada integração
COMPLETED pode incluir um objeto metadata opcional sobre a estrutura base. As necessidades de saída estruturada variam conforme o cliente, então o seu formato não está definido de forma fixa neste guia e deve ser acordado com o seu contato no Marco antes de implementar. O exemplo acima ilustra o padrão, não um esquema. CANCELLED e ERROR usam sempre a estrutura base.
Os erros ocorrem em dois momentos e se apresentam de forma diferente em cada um. Na criação da sessão são respostas HTTP padrão à sua chamada de servidor a servidor.
| Código | HTTP | Causa comum |
|---|---|---|
invalid_client_secret |
401 | A credencial do cabeçalho Authorization está ausente ou incorreta. |
unauthorized_origin |
403 | A configuração resolvida da conta não permite este ambiente. |
invalid_request |
400 | Um campo obrigatório não foi incluído ou estava malformado. |
rate_limited |
429 | Chamadas de criação de sessão demais em um intervalo curto. |
A partir da validação do iframe, os erros chegam como um evento de estado ERROR com os campos code e message.
| Código | Causa comum |
|---|---|
token_expired |
O iframe carregou depois que o TTL do launch_token expirou. |
token_already_used |
O launch_token já havia sido consumido. |
invalid_origin |
O iframe carregou a partir de um domínio fora da sua lista de permitidos. |
session_not_found |
Nenhuma sessão corresponde ao token, ou a sessão já foi encerrada. |
Tanto a criação de sessão quanto a incorporação do iframe acontecem apenas por HTTPS. O conteúdo misto é rejeitado pelo navegador antes que o Marco participe. Cada validação de token compara a origem solicitante com os domínios autorizados para a sua conta, e rejeita com unauthorized_origin mesmo que o token seja válido.
Um launch_token válido apenas confirma que o client_secret do seu backend foi apresentado corretamente. Não garante que aquele user_id deva ter acesso ao registro descrito em metadata: essa verificação é responsabilidade exclusiva da sua aplicação, no momento em que o seu backend chama o endpoint. Uma vez criado o token, a decisão de acesso já foi tomada; o token apenas a transmite.
Neste fluxo a sua página nunca chama o Marco diretamente: só o fazem o seu backend e o próprio iframe. Por isso a CSP da sua página contêiner só precisa autorizar a origem do Marco em frame-src.
Content-Security-Policy:
frame-src https://production.deep-talk.ai;
O iframe mantém a sua credencial de sessão em memória depois do início e a anexa explicitamente a cada chamada posterior, evitando as restrições de cookies de terceiros.
Comum aos três fluxos
O Marco não utiliza o conteúdo das conversas nem o metadata das sessões para treinar, ajustar ou melhorar nenhum modelo.
O conteúdo da sessão e os registros de auditoria são retidos por prazos diferentes, porque atendem a propósitos diferentes.
| Dados | Propósito | Retenção |
|---|---|---|
| Conteúdo da sessão | metadata, transcrição e resumo. Serve à consulta em si. |
Eliminado dentro de 48 horas após a sessão atingir um estado terminal. |
| Registros de auditoria | Monitoramento de segurança e evidência de conformidade. session_id, external_id quando se aplica, timestamps e estado final, sem conteúdo clínico. |
6 anos desde a criação, alinhado ao padrão do setor para registros de acesso em saúde. |
session_id é o identificador de correlação nos três fluxos, e vincula os eventos de uma sessão, as entradas de auditoria e o payload de COMPLETED. No fluxo de página privada, external_id o acompanha quando usado. Não existe um ID de correlação independente.
Estabilidade dos códigos de erro
Nos três fluxos, code é um identificador estável e seguro para usar em lógica de negócio; novos códigos podem ser adicionados com o tempo, então inclua sempre um caso padrão. message é para logs e depuração: o seu texto exato pode mudar e não deve ser comparado por padrão.
Comum aos três fluxos
O Marco oferece dois ambientes, com os mesmos caminhos em ambos os hosts.
| Ambiente | Host |
|---|---|
| Homologação (staging) | staging.deep-talk.ai |
| Produção | production.deep-talk.ai |
// Página pública e página privada
Script: https://<host>/widget/widget.js
API base URL: https://<host>/widget
// Servidor a servidor
Criação de sessão: POST https://<host>/launch_token
Iniciar chat: GET https://<host>/chat?launch_token=...
Cada ambiente tem as suas próprias credenciais e a sua própria lista de origens autorizadas, e não existem rotas entre ambientes para nenhuma delas.
| Fluxo | Credencial por ambiente |
|---|---|
| Página pública | Um appId próprio. Um appId de homologação não é válido contra o host de produção. |
| Página privada | Um segredo compartilhado próprio. Uma assinatura calculada com o segredo de homologação é rejeitada em produção. |
| Servidor a servidor | Um client_secret próprio. Um launch_token só é válido no ambiente que o emitiu. |
Guarde e rotacione as credenciais de cada ambiente separadamente.
As métricas de desempenho e os limiares fixos, incluindo o tempo até o primeiro token e qualquer outra garantia de latência acordada, são definidos no acordo de SLA, um documento independente deste guia.
Comum aos três fluxos
Antes de ativar qualquer um dos três fluxos, compartilhe o seguinte com o seu contato no Marco.
| O que precisamos | Para quê | Aplica-se a |
|---|---|---|
| Origem(ns) onde o widget é incorporado | Verificação de origem. É a lista contra a qual cada carregamento ou chamada é comparado. | Os três fluxos |
| Cores de marca primária e secundária | Personalizar o tema da interface para combinar com o seu site. | Os três fluxos |
| Duração da sessão | Tempo limite de inatividade e duração máxima, se os valores padrão não servirem. | Os três fluxos |
Formato do objeto metadata em COMPLETED |
Acordar a saída estruturada que a sua integração precisa receber ao encerrar a sessão. | Servidor a servidor |
O Marco entrega em troca as credenciais de cada ambiente: o appId no fluxo de página pública, o segredo compartilhado na página privada, e o client_secret junto com o app_id em servidor a servidor.
Dúvidas sobre qual fluxo é o seu?
Escreva para sales@marco.care com uma descrição de onde a sua aplicação vive e quais dados de contexto você precisa passar. Confirmamos o fluxo e entregamos as credenciais de homologação para que você possa testar antes de se comprometer.
