Smartflow
O Smartflow é o funnel de cadastro/KYC embutido do SDK Zarv. Sua configuração é imperativa: você chama Zarv('smartflow', config), que monta o funnel como um iframe na sua página.
🎮 Abrir o playground → — um embed real, com a sua chave de campanha: teste tela isolada, prefill, modal, tema,
identifyecomplete, e veja cada callback disparar.
Configuração básica
<div id="zarv-smartflow"></div>
<script async src="https://js.zarv.com/sdk.js"></script>
<script>
Zarv("smartflow", {
publicKey: "pk-XXXXXXXX", // a chave pública do flow (admin Zarv)
mount: "#zarv-smartflow", // embed inline; ou mode: 'modal'
onComplete: ({ leadId }) => console.log("finalizou", leadId),
onDecision: ({ leadId, status }) => console.log("decisão", status), // approved | rejected | manual
});
</script>O campo publicKey é obrigatório: é a chave pública do flow, no formato pk-<id>, obtida no admin Zarv (aba Avançado da campanha). Ela pode ser regenerada no admin — ao regenerar, a chave antiga para de funcionar imediatamente e qualquer embed que ainda a use deixa de carregar até você atualizar o snippet.
Opções de configuração
| Campo | Tipo | Descrição |
|---|---|---|
publicKey | string | Obrigatório. Chave pública do flow (pk-<id>) do admin. |
mount | string | Seletor CSS do elemento onde o funnel é embutido inline. |
mode | 'inline' | 'modal' | Forma de exibição: embutido na página ou em modal centralizada. |
height | number | Altura, em px, do embed inline. Por padrão há auto-resize ao conteúdo. |
modal | { width?, height?, margin? } | Ajustes da modal. A modal é sempre centralizada; por padrão ajusta à altura do conteúdo (auto-resize). height fixa a altura. |
screen | 'face' | 'cnh' | 'address' | 'email' | 'phone' | Embute UMA tela isolada em vez do fluxo inteiro. Omita para o fluxo completo. |
leadId | string | Usa um lead existente. Omita para iniciar uma auto-sessão (um lead é criado automaticamente). |
prefill | { name?, nationalId?, birthDate?, phone?, email? } | Persiste dados no lead, preenchendo a próxima tela. phone e email a partir do SDK 0.40.0. |
theme | 'light' | 'dark' | 'auto' | Tema da interface. |
closeOnComplete | boolean | Fecha a modal ao finalizar. Default: true em mode: 'modal', false inline. Com onDecision registrado, o fechamento automático espera a decisão (fechar antes derrubaria o iframe que a acompanha). |
onStep | function | Callback onStep(step, { progress, leadId }) a cada passo. |
onProgress | function | Callback onProgress(progress) com o progresso do funnel. |
onComplete | function | Callback onComplete({ leadId, status? }) ao concluir o fluxo. Dispara uma vez; status vem preenchido quando a decisão já é conhecida nesse momento. |
onDecision | function | Callback onDecision({ leadId, status }) quando a decisão da verificação chega: approved, rejected ou manual. Veja Fim do fluxo. |
onClose | function | Callback onClose() quando a modal é dispensada pelo usuário. |
onSession | function | Callback onSession({ leadId, externalId? }) assim que a sessão é criada, antes de o iframe renderizar. É como você obtém o leadId para encadear várias telas. O externalId vem preenchido quando o lead retomado já tinha uma referência sua. SDK 0.40.0+ (externalId em 0.41.0). |
onError | function | Callback onError({ error, status }) quando o embed não conseguiu montar (falha HTTP no bootstrap, erro de rede, ou seletor de mount inexistente). SDK 0.40.0+. |
Posicionamento
Você pode embutir o funnel inline na página ou exibi-lo em uma modal.
Inline — informe mount com o seletor do elemento alvo (ou use mode: 'inline'). Por padrão o iframe faz auto-resize, acompanhando a altura do conteúdo; use height para fixar uma altura em px:
Zarv("smartflow", {
publicKey: "pk-XXXXXXXX",
mount: "#zarv-smartflow",
// height: 640, // opcional; sem isso, auto-resize ao conteúdo
});Modal — use mode: 'modal'. A modal é sempre centralizada e, por padrão, ajusta à altura do conteúdo da tela (auto-resize). Use modal para ajustar largura, altura e margem:
Zarv("smartflow", {
publicKey: "pk-XXXXXXXX",
mode: "modal",
modal: { width: 480, margin: 24 }, // height fixa a altura, se informado
onClose: () => console.log("modal fechada"),
});modalSteps — mesmo embutido inline, você pode promover telas específicas (ex.: liveness / captura de documento) para uma modal overlay, listando os nomes das etapas:
Zarv("smartflow", {
publicKey: "pk-XXXXXXXX",
mount: "#zarv-smartflow",
modalSteps: ["face", "cnh"], // essas etapas abrem em modal; as demais ficam inline
});Aparência
O funnel aplica a identidade visual da campanha (logo, cores, alinhamento, fundo) na própria página — você não configura nada disso pelo SDK.
O que o SDK faz, a partir do SDK 0.41.0, é usar duas dessas configurações no enquadramento que ele desenha em volta do iframe:
bgColorpinta o card da modal e o overlay promovido. Sem isso, um funnel com tema escuro aparecia dentro de um card branco — branco nos cantos arredondados e um flash branco antes do primeiro paint.buttonShapedefine o canto do botão de fechar, que é um botão como os outros da campanha.
Nada a fazer do seu lado: os valores vêm da campanha e são aplicados no mount.
Tela única (componentes isolados)
Use screen para embutir uma única tela em vez do fluxo inteiro:
Zarv("smartflow", {
publicKey: "pk-XXXXXXXX",
mount: "#zarv-smartflow",
screen: "face",
onComplete: ({ leadId }) => console.log("face concluída", leadId),
});Valores possíveis: 'face', 'cnh', 'address', 'email', 'phone'. Omita screen para rodar o fluxo inteiro.
As telas são chamadas de forma independente — elas não seguem a ordem do fluxo. Ao concluir uma tela, o SDK dispara onComplete para que você trate o próximo passo do seu lado. A ordem do funnel é respeitada apenas pela versão hosted, não pelo SDK.
Várias telas no seu formulário
O caso mais comum de tela única é rodar várias delas como etapas do seu próprio cadastro — por exemplo, validar telefone, e-mail, facial e CNH dentro do formulário que você já tem.
Todas as telas precisam cair no mesmo lead. O encadeamento funciona assim:
- A primeira chamada vai sem
leadId. O backend cria o lead e devolve o id emonSession({ leadId }). - Você guarda esse
leadIde o passa em todas as chamadas seguintes. - No mesmo
onSession, umidentifyamarra o seu identificador (externalId) ao lead — é por ele que o seu backend vai reconhecer o resultado depois.
const SEQUENCIA = ["phone", "email", "face", "cnh"];
let leadId = null;
function etapa(i) {
if (i >= SEQUENCIA.length) return finalizar();
const screen = SEQUENCIA[i];
const config = {
publicKey: "pk-XXXXXXXX",
screen,
mount: "#slot-" + screen,
onSession: ({ leadId: id }) => {
if (leadId) return;
leadId = id;
// Amarra a sua referência (pedido, reserva, matrícula…) ao lead.
Zarv("smartflow", {
action: "identify",
leadId,
externalId: "RES-00042",
name: cadastro.nome,
nationalId: cadastro.cpf,
birthDate: cadastro.nascimento,
});
},
onComplete: () => etapa(i + 1),
onError: (err) => mostrarFallbackProprio(err),
};
if (leadId) config.leadId = leadId;
else config.prefill = {
name: cadastro.nome,
nationalId: cadastro.cpf,
birthDate: cadastro.nascimento,
phone: cadastro.telefone,
email: cadastro.email,
};
Zarv("smartflow", config);
}
function finalizar() {
Zarv("smartflow", {
action: "complete",
leadId,
metadata: { reserva: "RES-00042" },
onComplete: () => aguardarDecisaoNoSeuBackend(),
onError: (err) => tratarErro(err),
});
}
etapa(0);Pontos que costumam morder:
- Passe
leadIdexplicitamente emidentifyecompletequando houver mais de uma tela na página. Sem ele, essas chamadas miram a última sessão montada — o que raramente é a que você quer. (leadIdnessas chamadas requer o SDK 0.40.0+.) - A ordem é sua. As telas isoladas não seguem a ordem do funnel: cada
onCompletedevolve o controle para você decidir a próxima. onErrornão é opcional aqui. Um embed que falha no meio do seu formulário deixa um espaço em branco; semonErrora sua página não tem como perceber e cair num caminho alternativo.- A decisão pode não chegar no browser. Ela vem do polling de dentro do iframe; se o visitante fecha a página ou você desmonta o último embed antes de a verificação terminar, o
onDecisionsimplesmente não dispara. Trate o webhook da campanha como o canal real — inclusive para confirmar cada etapa, via os eventoslead.phone.completed,lead.email.completed,lead.face.completedelead.cnh.completed.
E a tela de dados básicos?
Ela não aparece quando você monta telas isoladas. O campo screen aceita apenas 'phone', 'email', 'face', 'cnh' e 'address' — não existe uma tela de nome/CPF/nascimento para embutir. Essa tela só existe no fluxo completo (sem screen).
Ou seja: se o seu cadastro já coletou esses dados, o visitante nunca os digita de novo. Mande-os na primeira chamada, por prefill, ou depois por identify:
// no mount da primeira tela
prefill: { name: "Ana", nationalId: "...", birthDate: "1990-01-02" }
// ou, em runtime, sobre um lead que já existe
Zarv("smartflow", {
action: "identify",
leadId,
name: "Ana",
nationalId: "...",
birthDate: "1990-01-02",
});O complete é o que cobra esses dados: ele responde 422 basic_data_incomplete enquanto o lead não tiver nome, CPF e nascimento. Se você receber esse erro mesmo tendo mandado prefill, é sinal de que algum dos três não chegou.
Fim do fluxo: onComplete e onDecision
Dois momentos distintos encerram um cadastro embutido:
onComplete({ leadId, status? })— o visitante chegou à tela final do funnel. A verificação (face, CNH, risco) pode ainda estar rodando; nesse casostatusvem ausente. Dispara uma única vez.onDecision({ leadId, status })— a decisão da verificação foi tomada:approved,rejectedoumanual(revisão humana). Se a decisão já era conhecida quando o visitante chegou à tela final, dispara imediatamente após oonComplete; caso contrário, dispara assim que o backend decide (o funnel acompanha por polling e avisa o SDK).
Quem decide o que acontece no fim é você: redirecionar a própria página, fechar a modal, mostrar UI própria. Sem handler, o funnel mostra a tela de resultado dentro do iframe (ou redireciona para as URLs de resultado configuradas na campanha, quando existirem).
Zarv("smartflow", {
publicKey: "pk-XXXXXXXX",
mode: "modal",
onDecision: ({ leadId, status }) => {
if (status === "approved") location.href = "/bem-vindo";
else if (status === "manual") mostrarBannerEmAnalise();
else location.href = "/nao-aprovado";
},
});Notas:
- Em
mode: 'modal'comonDecisionregistrado, ocloseOnCompleteespera a decisão para fechar — fechar noonCompletederrubaria o iframe cujo polling é a fonte da decisão. - O
onDecisioné o sinal de UX no browser; o canal autoritativo server-side continua sendo o webhook da campanha (use-o para efeitos de negócio, não o callback). - Com o autopilot desligado na campanha, todo cadastro finaliza como
manual— a aprovação/reprovação acontece depois, pela análise humana, e chega num segundo evento de webhook. No browser, oonDecisiondesses cadastros reportamanual: é o estado final que o visitante vê, não o desfecho.
Contexto do lead
Para continuar um cadastro já iniciado, passe leadId com um lead existente. Se você omitir leadId, o SDK inicia uma auto-sessão e cria um lead automaticamente.
O leadId criado chega no callback onSession, disparado assim que a sessão existe — antes de o iframe renderizar. É esse o momento de guardá-lo: os demais callbacks (onStep, onComplete) só chegam depois que o visitante interage, o que é tarde demais se você precisa montar a próxima tela no mesmo lead.
Prefill de identidade
Use prefill para persistir dados no lead, preenchendo a próxima tela:
Zarv("smartflow", {
publicKey: "pk-XXXXXXXX",
mount: "#zarv-smartflow",
prefill: {
name: "Ana",
nationalId: "...",
birthDate: "1990-01-02",
phone: "+5511999998888", // SDK 0.40.0+
email: "[email protected]", // SDK 0.40.0+
},
});phone e email importam sobretudo nas telas isoladas 'phone' e 'email': sem eles o visitante redigita um dado que você já tem, e pode digitar um valor diferente do que está no seu cadastro — quebrando a correspondência entre o que você guardou e o que foi verificado.
Prefill não pula a verificação: ele preenche o campo, então a tela de digitar é ignorada e o visitante cai direto na confirmação, que continua exigindo o código. Um telefone ou e-mail malformado é recusado na hora, com invalid_prefill_phone / invalid_prefill_email.
O prefill só faz sentido na primeira chamada de uma sessão. Depois que o lead existe, os dados já estão persistidos, e o backend nunca sobrescreve um campo já preenchido.
Identify em runtime
Depois de iniciar uma sessão (mesmo sem leadId), você pode identificar o usuário e anexar o seu próprio externalId:
Zarv("smartflow", {
action: "identify",
leadId, // opcional; obrigatório na prática se houver mais de uma tela na página
externalId: "id-do-parceiro-123",
name: "Ana",
nationalId: "...",
birthDate: "1990-01-02",
});Essa chamada atualiza o lead: ela preenche apenas os campos que ainda estão vazios, enquanto o externalId é sempre setado. Assim, a próxima tela já vem preenchida com os dados informados.
Sem leadId, a chamada mira a sessão ativa — a última tela montada. Com uma única tela na página isso é o esperado; com várias, informe o leadId (obtido em onSession) para não acertar o lead errado. O campo leadId requer o SDK 0.40.0+.
Complete em runtime
Quando o seu fluxo termina, chame complete para finalizar o cadastro: ele anexa metadados que você coletou no seu lado e dispara a verificação do usuário na sessão ativa.
Zarv("smartflow", {
action: "complete",
leadId, // opcional; veja a nota sobre sessão ativa em Identify em runtime
metadata: { pedido: "ABC-123", plano: "pro" }, // anexado em metadata.custom no lead
onComplete: ({ leadId, status }) => {
// verificação disparada (a decisão chega depois via webhook da campanha)
},
onError: (err) => {
if (err.error === "basic_data_incomplete") {
// o usuário ainda não informou os dados básicos (nome / CPF / nascimento)
}
},
});| Campo | Tipo | Descrição |
|---|---|---|
action | 'complete' | Obrigatório. |
leadId | string | Lead alvo. Omitido, usa a sessão ativa (a última tela montada). SDK 0.40.0+. |
metadata | object | Metadados adicionais do seu fluxo; persistidos sob metadata.custom no lead (vão também no webhook). |
onComplete | ({ leadId, status }) => void | Disparado quando a verificação é acionada com sucesso. |
onError | ({ error, status }) => void | Disparado em erro. |
Comportamento:
- Se o usuário não finalizou os dados básicos (
IsBasicDataCompletedé falso), ocompleteretorna errobasic_data_incomplete(HTTP 422) noonError— nada é publicado. - A verificação é disparada conforme o estado do lead (ex.: após o face capture, a re-validação de liveness/face no ZarvID). É idempotente: um lead que já está em verificação/decidido não é re-enfileirado.
- A decisão final (aprovado / em revisão / reprovado) não volta por este
onComplete— ela chega de forma assíncrona pelo webhook da campanha e, no browser, pelo callbackonDecisiondo mount.
Identificação do SDK
O SDK se identifica ao funnel com o parâmetro sdk=<versão> na URL do iframe (ex.: sdk=0.38.0). O funnel propaga esse parâmetro por toda a sessão e:
- registra
sdk_versionnos metadados do lead (junto deutm_*/referrer), permitindo segmentar cadastros por superfície de aquisição (SDK embed vs. funnel hosted) e por versão do SDK; - marca os eventos de analytics do funnel com
sdk_version; - ativa o modo embed automaticamente (não é preciso nenhum parâmetro adicional).
Nada a configurar do seu lado — o parâmetro é adicionado e transportado automaticamente.
Origens de embed (frame-ancestors)
Por padrão, o funnel permite ser embutido em js.zarv.com e js.zarv.dev (as origens oficiais do SDK) e nos domínios ativos do próprio flow.
Se um parceiro embutir o funnel em um host diferente do domínio do flow, é preciso adicionar essa origem em "AllowedEmbedOrigins" do flow (no admin). Sem isso, o navegador bloqueia o iframe via frame-ancestors.