Criar conta grátis

Documentação
Ver em Markdown

O Widget

O Widget

São 2 passos. No backend, você cria a sessão do usuário no Flow, enviando o seu reference_id (o id do usuário no seu sistema; obrigatório: ele volta no webhook e na API para você correlacionar). A resposta traz o id da sessão. No front, você monta o widget com esse id. Nenhum segredo vai ao navegador (o único segredo é a sua sk_, que fica no seu backend). O ambiente (sandbox × produção) é definido pela chave secreta usada na criação da sessão. Os módulos são os do Flow. Você não os declara no widget.

HTMLA forma de uma linha (sem JavaScript seu)no navegador
<!-- Se voce so precisa montar o widget e nada mais, o proprio <script> faz tudo:
     basta o data-session. Nenhum segredo aqui, como no exemplo acima: o id da sessao
     vem do SEU servidor, e a sua chave sk_ nunca sai de la. -->
<div id="identidade"></div>
<script
  src="https://api.unifokal.com/dist/widget.global.js"
  data-session="vs_01J8ZQ4S8M2N3P4Q5R6S7T8U9V"
  data-container="#identidade"
  crossorigin="anonymous"
></script>

<!-- O QUE ESPERAR DE VOLTA: o widget monta sozinho no #identidade assim que o script carrega.
     Sem data-session ele nao faz nada, em silencio: nao ha o que montar sem sessao. -->

A versão com o seu backend criando a sessão, os cabeçalhos que a câmera exige e o tratamento de erro está inteira em Integração ponta a ponta.

Para quem trava numa etapa, a central de ajuda explica cada mensagem do widget e o que fazer para seguir. Vale um link para ela na tela de ajuda do seu app.

COMO O WIDGET APARECE NA SUA PÁGINA

São três formas, e você escolhe no próprio trecho. Embutido: o card entra no lugar que você reservou. Botão flutuante: um botão fixo num canto abre a verificação, sem mexer no seu layout. Aberto pelo seu botão: o botão continua seu, e o widget abre quando alguém clica nele. Em qualquer uma delas o widget aparece do mesmo jeito para quem vai se verificar, e o seu site não consegue escondê-lo sem querer: ele se afirma na tela mesmo dentro de um layout que apaga tudo à volta.

Quem já integrou não precisa mudar nada. Reservar um lugar para o widget já é escolher o modo embutido, e é o que continua valendo. O painel também tem o seletor, em Customização, para quando você preferir decidir sem mexer no código; o que está escrito no trecho vence o painel.

HTMLEmbutido no seu layoutno navegador
<!-- Reservar um lugar ja e escolher este modo: nao precisa de data-mode.
     E o comportamento de sempre, e nada muda para quem ja integrou. -->
<div id="identidade"></div>
<script
  src="https://api.unifokal.com/dist/widget.global.js"
  data-session="vs_01J8ZQ4S8M2N3P4Q5R6S7T8U9V"
  data-container="#identidade"
  crossorigin="anonymous"
></script>

<!-- O QUE ESPERAR DE VOLTA: o card aparece dentro do #identidade assim que o script carrega, e a
     verificacao comeca na hora. -->
HTMLBotão flutuante (sem reservar lugar na página)no navegador
<!-- Nao precisa de <div>: o botao e o painel nascem no fim do <body> e se posicionam
     contra a janela, entao nenhum layout seu precisa mudar. Cole onde preferir, inclusive
     no <head>: se o corpo da pagina ainda nao existir, o widget espera ele existir. -->
<script
  src="https://api.unifokal.com/dist/widget.global.js"
  data-session="vs_01J8ZQ4S8M2N3P4Q5R6S7T8U9V"
  data-mode="floating"
  data-position="bottom-right"
  crossorigin="anonymous"
></script>

<!-- O QUE ESPERAR DE VOLTA: um botao fixo no canto escolhido. A verificacao comeca quando a pessoa
     clica, nao quando a pagina carrega. Cantos: bottom-right (padrao), bottom-left,
     top-right, top-left. Valor fora da lista e ignorado e cai no padrao. -->
HTMLAberto pelo SEU botãono navegador
<!-- O botao e seu: estilo, texto e lugar continuam seus. Duas formas de ligar, e as
     duas valem ao mesmo tempo. -->
<button id="verificar">Verificar identidade</button>
<script
  src="https://api.unifokal.com/dist/widget.global.js"
  data-session="vs_01J8ZQ4S8M2N3P4Q5R6S7T8U9V"
  data-mode="trigger"
  data-trigger="#verificar"
  crossorigin="anonymous"
></script>

<!-- Sem seletor: marque qualquer elemento com data-idsaas-open. Funciona junto. -->
<a href="#" data-idsaas-open>Comecar verificacao</a>

<!-- O QUE ESPERAR DE VOLTA: o clique abre o painel. Vale para elementos criados DEPOIS da carga
     (o ouvinte e um so, no documento), entao funciona em pagina que troca de tela sem
     recarregar. Seletor invalido e ignorado, e a forma por atributo continua valendo. -->
ANTES DE COLAR: REGISTRE A ORIGEM DO SITE

O widget só carrega em origens que você registrou. No painel, em Organização, origens do widget, cadastre cada endereço onde ele vai rodar (por exemplo https://app.suaempresa.com e o seu ambiente de homologação). Origem não registrada é recusada pelo navegador no preflight, e o widget simplesmente não sobe naquele site. O bloqueio aparece no console do navegador como erro de CORS, não como erro da nossa API, então é fácil procurar no lugar errado.

A tela grava em PUT /v1/dashboard/organization/allowed-origins, que é uma rota de painel: a sua sk_ não a alcança, e isso é de propósito. Ligar o produto num domínio novo é operação de conta, não chamada de servidor.

Isso vale para toda origem: o seu domínio de produção, o de homologação e o endereço que você usa no desenvolvimento. Cada uma entra como origem completa (esquema, host e porta), até 20 por conta.

O QUE O TITULAR VÊ ENQUANTO A CONFERÊNCIA ACONTECE

Depois de enviar as fotos, o titular fica numa tela de espera que anda: o indicador se move, o widget conta quantas conferências já concluíram e, se a espera passar de alguns segundos, a mensagem muda para uma que pede para ele continuar na tela. Quando o conjunto é fixo, a contagem sai como 2 de 3; quando o seu Flow decide etapas pelo caminho, sai só o quanto já andou, porque prometer um total que muda seria pior do que não prometer nenhum. Nada disso diz o que estamos conferindo nem revela resultado parcial: é andamento, não veredito.

Você não configura nem liga nada para isso, e não custa nada. Vale para a tela do aparelho que capturou; no computador que só mostra o QR Code, a tela continua sendo a de siga no celular. A contagem é apresentação: a verdade do desfecho continua sendo o seu webhook.

BOTÃO DE ATENDIMENTO DENTRO DA VERIFICAÇÃO

Quando o titular trava (documento recusado, selfie reprovada, análise pendente), o widget pode mostrar um botão que abre o seu canal de atendimento, com o reference_id daquela sessão já dentro do link. O atendente é seu: nós não hospedamos chat e não cobramos por isso, está incluso. Você liga no painel, em Customização, informando o endereço do seu canal (WhatsApp, Crisp, Intercom, Zendesk, a sua central) e o texto do botão.

O endereço é um modelo: onde você escrever {reference_id}, nós colocamos o identificador daquela sessão. Duas regras que a validação aplica no save, e vale saber antes de colar: o endereço precisa ser https, e o {reference_id} só pode aparecer na query ou no fragmento, nunca no host nem no caminho. Exemplos que passam: https://suporte.suaempresa.com/chat?ref={reference_id} e https://wa.me/5511999999999?text=ajuda%20{reference_id}.

Preferimos que você use o fragmento (#ref= em vez de ?ref=): o fragmento não é enviado ao servidor de destino, então o identificador do titular fica fora do log HTTP do seu fornecedor de chat e continua legível pelo JavaScript dele. O wa.me precisa de query e continua valendo.

Três coisas que a validação recusa de propósito, e por quê: chave secreta na URL (parâmetros como token, api_key ou secret), porque não guardamos segredo de terceiro; o nosso próprio domínio, porque o titular veria a nossa marca na barra de endereço de uma página que não é nossa; e o placeholder no host ou no caminho, porque ali um reference_id mal-intencionado mudaria o destino final do link. O titular vê o host de destino escrito ao lado do botão antes de clicar, e a verificação continua aberta na aba dele.

O identificador só acompanha o link nas primeiras 24 horas da sessão. Depois disso o botão continua aparecendo, e o link continua abrindo o seu canal, mas sem o reference_id: o atendente identifica pelo próprio canal. Isso existe para que um endereço de sessão vazado não vire uma consulta permanente ao identificador do titular.

LEMBRETE DE VERIFICAÇÃO NÃO CONCLUÍDA

Quando o seu fluxo tem verificação de e-mail por código e o titular confirma o código mas abandona a verificação em seguida, enviamos um único lembrete para aquele endereço, ainda dentro do prazo em que a sessão pode ser concluída. É um por verificação, para sempre: não existe segunda cobrança, nem sequência.

Três coisas que esse e-mail não faz, e vale saber antes de perguntar: ele não carrega link de acesso à verificação (o titular volta pela sua página, e nenhuma credencial de sessão viaja por e-mail), não contém dado do documento nem resultado, e não vai para quem apenas digitou um endereço sem confirmar o código. Endereço não confirmado pode ser de outra pessoa, e mandar mensagem para ela seria o defeito, não a feature.

O titular pode recusar novos lembretes pelo próprio e-mail. Quando ele recusa, guardamos só um código derivado do endereço, nunca o endereço, e a recusa vale para toda a plataforma, não apenas para a sua conta. A cláusula C.14 da Política de Privacidade descreve o tratamento. Lembrete por SMS não existe hoje.

O QUE A SUA PÁGINA PRECISA PERMITIR

O widget roda dentro da sua página, e é ela que abre a câmera. Então a sua página precisa: ser servida por https (o navegador não entrega câmera em http, salvo em localhost), não negar a câmera no cabeçalho Permissions-Policy, e ter uma Content-Security-Policy que permita o nosso bundle, a nossa API, o canal de tempo real e os workers do detector de rosto. O exemplo em Integração ponta a ponta traz os cabeçalhos prontos.

Uma ressalva sobre Trusted Types. O widget roda no seu documento, não em um iframe, então uma política Trusted Types que você publique alcança o nosso código junto com o seu. Hoje, require-trusted-types-for 'script' em modo de bloqueio na sua página interrompe a captura. O sintoma é ruim de diagnosticar: a tela fica em branco e o erro sai só no console do navegador, longe de qualquer log nosso. Se a sua política exige Trusted Types, use Content-Security-Policy-Report-Only na rota da verificação enquanto avalia, e fale com a gente antes de ligar o bloqueio. Essa decisão é sua, e o cabeçalho é da sua página: nós não temos como relaxá-lo do nosso lado.

Quer fixar a versão do bundle (SRI)? A URL acima é a móvel: ela sempre serve a versão atual, com no-cache, e é a escolha certa para a maioria (você recebe correção nossa sem fazer nada). Se a sua política exige Subresource Integrity, use a URL versionada: ela é imutável por versão e vem com o hash publicado por nós. O par (URL, hash) está sempre em https://api.unifokal.com/dist/integrity.json, que é gerado no nosso build a partir dos bytes servidos. Nunca copie um hash de um texto (inclusive deste): leia do manifesto, de preferência pelo seu CI.

# 1) leia o par URL + hash do manifesto (gerado no nosso build)
curl -s https://api.unifokal.com/dist/integrity.json
{
  "_leia": "Par URL + integrity do bundle do widget UNIFOKAL, gerado no build …",
  "version": "b1efde7e5c593de78",
  "files": {
    "widget.global.js": {
      "url": "/dist/b1efde7e5c593de78/widget.global.js",
      "integrity": "sha384-…",
      "bytes": 166951
    },
    "widget.esm.js": {
      "url": "/dist/b1efde7e5c593de78/widget.esm.js",
      "integrity": "sha384-…",
      "bytes": 166820
    }
  }
}
# são DOIS artefatos: widget.global.js (a tag <script>, expõe IdSaas) e widget.esm.js
# (import de módulo, para quem empacota o próprio front). O par URL + integrity é por arquivo.

# 2) use os dois JUNTOS na página (o crossorigin é obrigatório para o SRI ser avaliado)
<script src="https://api.unifokal.com/dist/b1efde7e5c593de78/widget.global.js"
        integrity="sha384-…"
        crossorigin="anonymous"></script>

O que isso significa na prática: a versão que você fixou não muda de conteúdo, nunca. Quando publicamos um bundle novo, a versão passa a ser outra e o manifesto muda junto, então fixar exige atualizar (por isso a recomendação de ler o manifesto no seu CI em vez de colar o hash na mão). Uma versão que sai do ar responde 404, e não outro conteúdo: preferimos falhar claro a servir bytes diferentes sob a URL que você assinou.

O EVENTO QUE O WIDGET DISPARA NA SUA PÁGINA

O widget avisa a sua página quando o estado da verificação muda, e avisa de novo quando ela acaba. São dois eventos, e os nomes são estáveis: unifokal:state a cada transição e unifokal:done uma única vez, no fim. É por eles que um aplicativo que abre a verificação num webview sabe a hora de fechar a tela, sem esperar o seu servidor avisar.

O mesmo aviso chega por três canais, e você escolhe o que couber: um evento no window da página (window.addEventListener), o onEvent que você passa no mount(), e window.ReactNativeWebView.postMessage quando a página está dentro de um webview React Native. O payload é o mesmo nos três: { event, state, outcome }. O outcome vem preenchido só no unifokal:done, e é null em qualquer aviso intermediário.

// A sua página, ouvindo o fim da verificação. Nada além disto é necessário.
window.addEventListener("unifokal:done", (ev) => {
  const { outcome } = (ev as CustomEvent<{ outcome: string }>).detail;
  // outcome: "approved" | "pending" | "declined" | "failed" | "expired" | "analysis_incomplete"
  fecharTelaDaVerificacao(outcome);
});

// Quem monta por JS recebe o mesmo payload pelo callback, sem ouvir o window:
IdSaas.mount({ sessionId: "vs_01J8ZQ4S8M2N3P4Q5R6S7T8U9V", onEvent: (e) => console.log(e) });

Duas garantias que você pode assumir. O aviso intermediário não se repete: enquanto o widget espera, ele reconsulta o estado a cada poucos segundos, e só uma transição de verdade vira evento. E o final é único: depois do unifokal:done nenhum outro evento sai daquele widget, então você pode fechar a tela sem se proteger de um segundo disparo. Um erro dentro do seu listener nunca derruba a verificação do titular.

O que o evento nunca carrega, e isso é desenho, não omissão: nenhuma credencial (o identificador da sessão é a credencial do widget, e ele não viaja por aqui), nenhum dado do titular, nenhum resultado de módulo e nenhuma mensagem de erro. Quem escuta pode estar fora do seu controle, porque é a página que hospeda o widget, e o que sai daqui é sinal de interface para fechar tela.

E a verdade do desfecho continua sendo o seu webhook. Este evento vive no navegador, e quem controla a página pode forjá-lo. Use-o para a experiência, nunca para liberar, creditar ou dar por concluído qualquer coisa do seu lado: isso se faz com a entrega assinada que chega ao seu servidor.

OPÇÕES DO mount()
campotipodescrição
sessionIdobrigatórioID da sessão criada no backend (vs_…). É a credencial do widget: nenhum segredo na página.
containeropcionalSeletor CSS onde o widget monta (default: #idsaas-widget).
baseUrlopcionalOrigem da API UNIFOKAL (default: a origem de onde o widget é servido).
localeopcionalDica de idioma da interface (por exemplo "en"). É só uma dica: o idioma da sessão, resolvido no servidor, vence, e um valor desconhecido é ignorado.
themeopcionalTema do widget: "light", "dark" ou "auto" (auto segue o modo claro/escuro do aparelho do titular, ao vivo). Sem o campo, vale a customização da sua conta; sem escolha nenhuma, o widget é claro como sempre. Valor desconhecido é ignorado.
accentopcionalCor de acento em hex (por exemplo "#7c5cff"): barra, botões e destaques daquela página. A tinta do texto sobre o acento (clara ou escura) é escolhida automaticamente conforme a cor. Valor fora do formato hex é ignorado e o widget fica na cor da conta.

Todo campo da tabela também existe na forma declarativa, como atributo data- do próprio <script>: data-session, data-container, data-base-url, data-locale, data-theme, data-accent. Com data-session presente o widget monta sozinho, sem você escrever uma linha de JavaScript. É a forma do exemplo acima.

Como o idioma é escolhido, do mais forte para o mais fraco: o idioma da sua conta, se você escolheu um em Customização; depois a dica desta página (locale ou data-locale); depois o idioma do navegador de quem acessa; e por último o português. Hoje o widget fala português, inglês e espanhol.

Escolha o idioma da conta quando o seu público fala uma língua só: ele vale para toda sessão criada a partir daquele momento, e fica congelado em cada sessão. Trocar a configuração no meio de uma verificação não muda a língua de quem já está com o widget aberto, de propósito. Deixando em detectar, cada pessoa vê o widget no idioma do próprio aparelho, que costuma ser a escolha certa para público misto.

TEMA (CLARO, ESCURO, AUTO) E COR DE ACENTO

O widget nasce claro e continua claro para quem não configura nada. Se a sua página tem modo escuro, peça dark (fixo) ou auto: no auto o widget segue o modo do aparelho do titular e acompanha a troca ao vivo. O accent muda a cor de destaque (barra, botões) só naquela página, sem mexer na customização da conta. Os dois são locais: viram classe e variável CSS no próprio widget e nunca viajam para a API.

// TypeScript, na sua página (a forma declarativa usa data-theme e data-accent):
IdSaas.mount({
  sessionId: "vs_01J8ZQ4S8M2N3P4Q5R6S7T8U9V",
  theme: "auto",      // "light" | "dark" | "auto"
  accent: "#7c5cff",  // hex; a tinta sobre o acento (clara ou escura) e escolhida automaticamente
});

A precedência é simples: o que a página pede vence a customização da conta; contas que já usam fundo escuro na tela de customização ganham o widget escuro sem mudar nada. Os dois temas mantêm contraste AA nos textos e ícones da paleta padrão. Valor fora da lista (theme) ou fora do formato hex (accent) é ignorado em silêncio: o widget cai no visual padrão, nunca quebra.

Pronto para integrar? A chave de sandbox sai no painel, logo depois do cadastro. Criar conta grátis