Onboarding e dropout
Descubra em qual tela do seu onboarding os usuários estão desistindo. O SDK rastreia cada step e o dashboard monta o funil automaticamente.
Como funciona
Em cada tela do seu fluxo de onboarding você marca um step, informando um nome e a posição dele no funil. Quando o usuário termina, chama complete(). Quem detecta o abandono é o servidor: você não precisa fazer nenhuma chamada para isso. O dashboard usa esses eventos pra montar o funil de conversão e mostrar onde o dropout está acontecendo.
O universo do funil são os installs do período, não só quem mandou evento de onboarding. Todo usuário instalado entra na base, e a posição dele no funil vem do step mais avançado que ele reportou. Quem nunca reportou step nenhum fica parado no step 0.
Antes de começar
- O SDK precisa estar inicializado antes de qualquer chamada de onboarding. Chamar antes disso lança
ONBOARDING_NOT_INITIALIZED. Se quiser garantir, aguardewaitUntilReady()antes do primeirostep(). - Se o SDK ainda não tem um
distinctId(init em andamento), o evento é descartado em silêncio. Mais um motivo pra esperar owaitUntilReady()em telas que aparecem logo no boot do app. - O dashboard fica em
/dropoute precisa da feature flag dropout ativa no seu app (fala com a gente se não tiver).
Marcando steps
Em cada tela do onboarding, chama step(nome, order) com um nome único e o order da tela no fluxo (0, 1, 2...). O segundo argumento é obrigatório: sem ele o backend não consegue montar a sequência do funil corretamente. Pode chamar dentro de um useEffect no mount da tela. Chamar de novo pro mesmo step (usuário voltou uma tela, por exemplo) não tem problema: o backend guarda o progresso mais avançado.
import { onboardingManager } from "@virex-tech/paywallo-sdk";
// chama em cada tela do seu onboarding, passando o order da tela
await onboardingManager.step("welcome", 0);
await onboardingManager.step("age_selection", 1);
await onboardingManager.step("goal", 2);Teste A/B
A forma recomendada de modelar variantes A/B é passar um variantKey (string) junto com o step. O order permanece um inteiro indicando a posição no funil. Não misture posição e variante no mesmo número. Passe o mesmo variantKey em complete() para fechar o funil identificado pela variante correta.
No Flutter, onboardingStep() não aceita variantKey. A variante se identifica pelo order decimal (explicado logo abaixo nesta seção), e o variantKey só existe em onboardingComplete().
import { onboardingManager } from "@virex-tech/paywallo-sdk";
// Forma recomendada para teste A/B: order inteiro (posição no funil) + variantKey (string)
await onboardingManager.step("pricing", 2, { variantKey: "control" });
await onboardingManager.step("pricing", 2, { variantKey: "variant_b" });
// Opcional: tempo gasto na tela anterior (em segundos)
await onboardingManager.step("goal", 2, { variantKey: "control", timeOnPrevS: 12 });No funil de dropout, steps com o mesmo order e variantKey diferente colapsam no step pai e aparecem como uma única barra. A comparação granular entre variantes fica por conta da tela de Testes A/B.
O campo order também aceita decimais (ex: 2.1, 2.2) como forma alternativa de identificar variantes: a parte inteira define a posição; o decimal, a variante. Prefira o variantKey pra novos fluxos: é mais legível e não sofre com arredondamento de ponto flutuante.
Passos que se dividem
Às vezes o onboarding ramifica: telas diferentes aparecem para usuários diferentes na mesma posição do fluxo. Um app de parar de fumar mostra "cigarros por dia" para fumantes e "puffs por dia" para quem usa vape; um app de peptídeos mostra uma tela para quem já tem protocolo e outra para quem não tem. Se você numerar cada tela com um orderinteiro sequencial, o funil "esparrama": a mesma etapa vira várias linhas, cada uma com só uma fração dos usuários.
A solução é dar o mesmo order inteiro à posição e um decimal a cada variante. O servidor arredonda o order para baixo, então todas as variantes entram na mesma etapa do funil, e o decimal registra qual tela cada usuário viu (visível no detalhamento do passo).
// Ramificacao: cada usuario ve UMA das telas na mesma posicao.
// Mesmo inteiro, decimal por variante.
await onboardingManager.step("cigsPerDay", 8.1); // fumante
await onboardingManager.step("puffsPerDay", 8.2); // vape
// No funil ambos contam como "passo 8"; o decimal aparece no detalhamento.Não confunda com o variantKey de teste A/B: o variantKey é para a mesma tela testando conteúdos diferentes (ex: dois preços). O decimal é para telas genuinamente diferentes que ocupam a mesma posição por causa de uma ramificação do fluxo.
Use decimais só para telas alternativas: o usuário vê uma ou outra, nunca as duas. Telas que todos veem em sequência devem ter orders inteiros distintos.
Ramificação por resposta
Opcional: se o seu onboarding faz uma pergunta que muda o caminho (ex: "qual é o seu objetivo?"), registre a resposta com branch. Cada chamada é um par pergunta → resposta; chamando a mesma pergunta de novo, a última resposta vence. Apps com onboarding linear não precisam disso.
import { onboardingManager } from "@virex-tech/paywallo-sdk";
// logo depois que o usuário responde a pergunta, antes dos steps do caminho dele
await onboardingManager.branch("objetivo", "perder peso");Chame logo depois que o usuário responde, antes dos steps do caminho escolhido. Limites: a pergunta (key) aceita letras, números e _ . -, de 1 a 64 caracteres; a resposta (value) é texto livre de 1 a 64 caracteres (espaços, acentos e emoji valem, ex: "ganhar músculo"); até 20 perguntas por usuário. Os valores __none__, __other__ e __all__ são reservados. Chamada inválida é ignorada com um aviso no console, nunca lança erro.
Não confunda com os outros dois recursos: o variantKey é para teste A/B da mesma tela, e os decimais de "Passos que se dividem" só separam telas alternativas dentro de um passo. O branch é por resposta do usuário e é ele que habilita filtrar e quebrar o funil no painel.
No painel, em Onboarding dropout, use o seletor Quebrar por perguntapara ver um funil por resposta, e os filtros para olhar só quem respondeu algo. A escolha fica na URL, então dá para compartilhar o link. "Antes da pergunta" reúne quem desistiu antes de responder; "Outros" agrupa as respostas além das 10 mais comuns.
Finalizando o fluxo
Quando o usuário chega no fim do onboarding (última tela, paywall, home), chama complete(). Isso marca o fluxo como concluído pra esse usuário, fecha o funil dele e alimenta o card de taxa de conclusão. Sem o complete() a taxa de conclusão fica em 0% mesmo com os steps chegando.
import { onboardingManager } from "@virex-tech/paywallo-sdk";
// quando o usuário termina o fluxo (chegou na última tela ou no paywall)
await onboardingManager.complete();
// Em teste A/B, passe o mesmo variantKey usado nos steps
await onboardingManager.complete({ variantKey: "variant_b" });Abandono (automático)
Não existe mais drop() no SDK. O abandono é inferido pelo servidor por tempo: se o usuário iniciou o onboarding, não chamou complete() e ficou mais de 1 hora sem avançar (sem novo step()), o servidor marca o fluxo como abandonado automaticamente. Fechar o app não conta como abandono: só o tempo sem avançar. Se o usuário voltar e mandar um novo step(), ele sai do estado de abandono e o fluxo continua normalmente.
Nomes de step
- Precisa ser uma string não vazia (senão a chamada lança
ONBOARDING_INVALID_STEP_NAME). - O agrupamento ignora maiúsculas e minúsculas:
introHomeeintrohomecaem no mesmo step. Renomear de verdade (trocar a palavra) quebra o histórico, então mantenha os nomes consistentes entre releases. - Use nomes descritivos da tela (
"age_selection","goal"), não genéricos tipo"step1". É esse nome que aparece no funil.
Exemplo completo
Um fluxo de 3 telas (welcome, age, goal):
import { useEffect } from "react";
import { onboardingManager } from "@virex-tech/paywallo-sdk";
function WelcomeScreen({ navigation }) {
useEffect(() => {
onboardingManager.step("welcome", 0);
}, []);
return (
<View>
<Text>Bem-vindo!</Text>
<Button title="Continuar" onPress={() => navigation.navigate("Age")} />
</View>
);
}
function AgeScreen({ navigation }) {
useEffect(() => {
onboardingManager.step("age_selection", 1);
}, []);
return (
<View>
<Text>Qual sua idade?</Text>
<Button title="Continuar" onPress={() => navigation.navigate("Goal")} />
</View>
);
}
function GoalScreen({ navigation }) {
useEffect(() => {
onboardingManager.step("goal", 2);
}, []);
return (
<View>
<Text>Qual seu objetivo?</Text>
<Button
title="Finalizar"
onPress={async () => {
await onboardingManager.complete();
navigation.navigate("Paywall");
}}
/>
</View>
);
}Você também pode usar o hook useOnboarding(), que expõe os mesmos step e complete com referências estáveis:
import { useOnboarding } from "@virex-tech/paywallo-sdk";
function AgeScreen() {
const { step, complete } = useOnboarding();
useEffect(() => {
step("age_selection", 1);
}, [step]);
// ...
}Quando o funil aparece
A mensagem "dados insuficientes" aparece no lugar do funil quando ele volta com no máximo uma etapa e nenhum usuário chegou ao onboarding no período. É o jeito de não exibir um "1 etapa · step_0 100%" enganoso. Não é sinal de amostra pequena: é sinal de que nenhum evento de step entrou nesse recorte. As causas de sempre são três: o período selecionado não cobre a build que você testou, o filtro de environment está em Production e a build é Sandbox, ou o app ainda não está emitindo os steps. Confira com debug: true que o step() sai do app, e revise os dois filtros antes de concluir que falta volume.
Validando a integração
- Inicialize o SDK com
debug: truenuma build de teste: cada evento aceito loga[Paywallo:Onboarding] event emittedno console, e eventos descartados (sem distinctId) logamskipped. - No dashboard, confira se o filtro de environment no topo da página bate com o ambiente da build que você está testando. O SDK detecta sandbox e production automaticamente, e evento marcado como sandbox não aparece com o filtro Production ativo.
- Se o funil mostra um único step_0 com 100% dos usuários e conclusão 0%, o app não está enviando os eventos de onboarding. Como o universo são os installs, o funil nunca fica vazio: sem eventos, todo mundo aparece empilhado no step 0 com um nome genérico.
- O nome real de um step aparece no funil quando ele acumula massa mínima no histórico do app. O piso é adaptativo (0,07% do total de steps, com teto de 20) e cai até 1 em app novo ou de baixo volume, justamente pro funil não travar em nome genérico. O
step_Ngenérico sobra, na prática, pra passo raro em app de volume alto.
Pegadinhas
- Mande o step pro Paywallo mesmo que você já rastreie onboarding em outra ferramenta (PostHog, Firebase, etc). Evento custom com outro nome não alimenta o funil: só a família canônica emitida por
step()ecomplete()conta. complete()marca o fluxo como concluído. Chame no máximo uma vez por usuário.- Os dados não são retroativos. Usuários que passaram pelo onboarding antes da integração continuam aparecendo no step 0, e o funil só reflete os steps reais pra quem instalou uma build com o SDK chamando
step().
Desenvolvido e mantido por Virex