Pular para o conteúdo principal

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. HTML · A forma de uma linha (sem JavaScript seu) · no navegador
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. Sem modo e sem lugar reservado, o widget entra embutido no fim da página, na hora. O painel monta o trecho com a forma que você escolher, em Customização e no construtor de flows, e vale o que está colado no seu site. Montando pelo JavaScript, a forma vai na mesma chamada: IdSaas.mount({ sessionId, mode: "floating", position: "bottom-left" }) para o botão flutuante e IdSaas.mount({ sessionId, mode: "trigger", trigger: "#verificar" }) para o seu botão. Sem mode, o widget monta embutido no container que você passar. HTML · Embutido no seu layout · no navegador
HTML · Botão flutuante (sem reservar lugar na página) · no navegador
HTML · Aberto pelo SEU botão · no navegador
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 detector compila WebAssembly, então o script-src precisa de 'wasm-unsafe-eval', que libera só isso; 'unsafe-eval' não é necessário. Sem a palavra, o desafio de prova de vida não avança. 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.
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.
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() 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.
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.