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, aguarde waitUntilReady() antes do primeiro step().
  • Se o SDK ainda não tem um distinctId (init em andamento), o evento é descartado em silêncio. Mais um motivo pra esperar o waitUntilReady() em telas que aparecem logo no boot do app.
  • O dashboard fica em /dropout e 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: introHome e introhome caem 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: true numa build de teste: cada evento aceito loga [Paywallo:Onboarding] event emitted no console, e eventos descartados (sem distinctId) logam skipped.
  • 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_N gené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() e complete() 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