Deep links para aplicativos móveis nativos: rotas, tokens e 'abrir no app'
Aprenda deep links para aplicativos móveis nativos: planeje rotas, trate o “abrir no app” e transfira tokens com segurança para Kotlin e SwiftUI sem código de roteamento personalizado confuso.

O que um deep link deve fazer, em termos simples
Quando alguém toca um link no celular, espera um resultado: ser levado ao lugar certo, imediatamente. Não a um lugar parecido. Não a uma tela inicial com uma barra de busca. Nem a uma tela de login que esquece por que a pessoa veio.
Uma boa experiência de deep link parece com isto:
- Se o app está instalado, ele abre exatamente na tela que o link indica.
- Se o app não está instalado, o toque ainda ajuda (por exemplo, abre um fallback web ou a página da loja e pode retornar a pessoa ao mesmo destino após a instalação).
- Se a pessoa precisa fazer login, ela faz login uma vez e vai para a tela pretendida, não para a tela inicial do app.
- Se o link carrega uma ação (aceitar convite, ver pedido, confirmar email), a ação é clara e segura.
A maior frustração vem de links que “mais ou menos funcionam” mas quebram o fluxo. Pessoas veem a tela errada, perdem o que estavam fazendo ou ficam presas num loop: tocar link, fazer login, cair no dashboard, tocar link de novo, logar de novo. Mesmo um passo a mais pode fazer o usuário desistir, especialmente para ações de uso único como convites ou redefinição de senha.
Antes de escrever qualquer código Kotlin ou SwiftUI, decida o que você quer que os links signifiquem. Quais telas podem ser abertas de fora? O que muda se o app está fechado vs já rodando? O que deve acontecer quando o usuário está deslogado?
O planejamento é o que previne a maior parte da dor depois: rotas claras, comportamento previsível de “abrir no app” e uma forma segura de transferir códigos de uso único sem pôr segredos diretamente na URL.
Tipos de deep link e onde o “abrir no app” dá errado
Nem todo “link que abre um app” se comporta da mesma forma. Trate-os como intercambiáveis e você vai esbarrar nas falhas clássicas: o link abre no lugar errado, abre um navegador em vez do app, ou funciona em apenas uma plataforma.
Três categorias comuns:
- Schemes customizados (por exemplo, um esquema específico como myapp:). São fáceis de configurar, mas muitos apps e navegadores os tratam com cautela.
- Universal Links (iOS) e App Links (Android). Usam links web normais e podem abrir o app quando instalado, ou cair em um site quando não estiver.
- Links em navegadores embutidos. Links abertos dentro de um app de email ou do navegador interno de um mensageiro. Muitas vezes se comportam diferente do Safari ou do Chrome.
“Abrir no app” pode significar coisas diferentes dependendo de onde o toque acontece. Um link tocado no Safari pode saltar direto para o app. O mesmo link tocado dentro de um email ou mensageiro pode abrir primeiro uma web view embutida, e a pessoa precisa apertar um botão extra “abrir” (ou nem vê esse botão). No Android, o Chrome pode respeitar App Links enquanto o navegador embutido de um app social pode ignorá-los.
Cold start vs app já rodando é a próxima armadilha.
- Cold start: o SO lança seu app, seu app inicializa, e só então você recebe o deep link. Se seu fluxo de inicialização mostra splash, checa auth ou carrega config remota, o link pode se perder a menos que você o armazene e o reproduza depois que o app estiver pronto.
- Já rodando: você recebe o link enquanto o usuário está em uma sessão. A pilha de navegação existe, então o mesmo destino pode exigir um tratamento diferente (fazer push de uma tela vs resetar a pilha).
Um exemplo simples: um link de convite tocado no Telegram muitas vezes abre primeiro num navegador embutido. Se seu app assume que o SO sempre fará o handoff direto, os usuários verão uma página web e vão presumir que o link quebrou. Planeje esses ambientes desde o começo e você escreverá menos cola específica por plataforma depois.
Planeje suas rotas antes de implementar qualquer coisa
A maior parte dos bugs de deep link não é problema de Kotlin ou SwiftUI. São problemas de planejamento. O link não mapeia limpidamente para uma tela, ou carrega opções demais e vagas.
Comece com um padrão de rota consistente que combine com a forma como as pessoas pensam sobre seu app: lista, detalhe, configurações, checkout, convite. Mantenha legível e estável — você vai reutilizar em emails, QR codes e páginas web.
Um conjunto simples de rotas pode incluir:
- Home
- Lista de pedidos e detalhes do pedido (orderId)
- Configurações da conta
- Aceitação de convite (inviteId)
- Busca (query, tab)
Então defina seus parâmetros:
- Use IDs para objetos únicos (orderId).
- Use parâmetros opcionais para estado de UI (tab, filter).
- Decida defaults para que todo link tenha um destino preferível.
Também decida o que acontece quando o link está errado: dados faltando, ID inválido ou conteúdo inacessível. O padrão mais seguro é abrir a tela estável mais próxima (como a lista) e mostrar uma mensagem curta. Evite jogar pessoas numa tela em branco ou numa tela de login sem contexto.
Por fim, planeje por fonte. Um QR code geralmente precisa de uma rota curta que abra rápido e tolere conectividade ruim. Um link de email pode ser mais longo e incluir contexto extra. Uma página web deve degradar graciosamente: se o app não estiver instalado, a pessoa ainda deve chegar a um lugar que explique o que fazer a seguir.
Se você usa uma abordagem guiada pelo backend (por exemplo, gerando endpoints de API e telas com uma plataforma como AppMaster), esse plano de rotas vira um contrato compartilhado: o app sabe para onde ir e o backend sabe quais IDs e estados são válidos.
Transferência segura de tokens sem colocar segredos na URL
Um deep link é frequentemente tratado como um envelope seguro. Não é. Qualquer coisa na URL pode acabar no histórico do navegador, em screenshots, prévias compartilhadas, logs de analytics ou copiada em um chat.
Evite colocar segredos no link. Isso inclui tokens de acesso de longa duração, refresh tokens, senhas, dados pessoais ou qualquer coisa que permita agir como o usuário se o link for reenviado.
Um padrão mais seguro é um código de uso único e curto. O link carrega apenas esse código, e o app troca-o por uma sessão real depois de abrir. Se alguém roubar o link, o código deve ser inútil depois de um ou dois minutos, ou após o primeiro uso.
Um fluxo simples de handoff:
- O link contém um código one-time, não um token de sessão.
- O app abre e chama seu backend para resgatar o código.
- O backend valida expiração, checa se não foi usado e então marca como usado.
- O backend retorna uma sessão autenticada normal para o app.
- O app limpa o código da memória após o resgate.
Mesmo depois de um resgate bem-sucedido, confirme a identidade dentro do app antes de fazer algo sensível. Se o link serve para aprovar um pagamento, alterar email ou exportar dados, peça uma checagem rápida como biometria ou um login recente.
Armazene a sessão resultante com segurança. No iOS, isso normalmente significa o Keychain. No Android, use armazenamento com Keystore. Guarde apenas o necessário e limpe no logout, remoção da conta ou quando detectar reutilização suspeita.
Um exemplo concreto: você envia um link de convite para entrar em um workspace. O link carrega um código one-time que expira em 10 minutos. O app resgata, então mostra uma tela que deixa claro o que acontecerá a seguir (entrar em qual workspace). Só depois que o usuário confirma é que o ingresso é concluído.
Se você está construindo com AppMaster, isso mapeia bem para um endpoint que resgata códigos e retorna uma sessão, enquanto sua UI móvel trata o passo de confirmação antes de qualquer ação de alto impacto.
Autenticação e “continuar de onde parou”
Deep links frequentemente apontam para telas com dados privados. Comece decidindo o que pode ser aberto por qualquer um (público) e o que requer sessão autenticada (protegido). Essa única decisão evita a maioria das surpresas do tipo “funcionou no teste”.
Uma regra simples: direcione primeiro para um estado de aterrissagem seguro, e então navegue para a tela protegida só depois de confirmar que o usuário está autenticado.
Decida o que é público vs protegido
Trate deep links como se pudessem ser encaminhados para a pessoa errada.
- Público: páginas de marketing, artigos de ajuda, início de redefinição de senha, início de aceitação de convite (sem dados mostrados ainda)
- Protegido: detalhes do pedido, mensagens, configurações de conta, telas de admin
- Misto: uma tela de pré-visualização pode ser ok, mas mostre apenas placeholders não sensíveis até o login
“Continuar após login” que retorna ao lugar certo
A abordagem confiável é: parseie o link, armazene o destino pretendido e então roteie com base no estado de auth.
Exemplo: um usuário toca num link “abrir no app” para um ticket de suporte específico enquanto está deslogado. Seu app deve abrir numa tela neutra, pedir para entrar e então levá-lo automaticamente para aquele ticket.
Para manter isso confiável, armazene um pequeno “return target” localmente (nome da rota mais um ID do ticket) com expiração curta. Depois que o login terminar, leia uma vez, navegue e então limpe. Se o login falhar ou o target expirar, volte para uma home segura.
Trate casos de borda com respeito:
- Sessão expirada: mostre uma mensagem curta, re-autentique e então continue.
- Acesso revogado: abra o invólucro do destino e então mostre “Você não tem mais acesso” e ofereça um próximo passo seguro.
Também evite mostrar dados privados em prévias na tela de bloqueio, no app switcher ou em notificações. Mantenha telas sensíveis vazias até que os dados carreguem e a sessão seja verificada.
Uma abordagem de roteamento que evita spaghetti de navegação customizado
Deep links ficam confusos quando cada tela faz seu próprio parse de URLs. Isso espalha pequenas decisões (o que é opcional, o que é requerido, o que é válido) por todo o app, e fica difícil mudar com segurança.
Trate roteamento como canos compartilhados. Mantenha uma tabela de rotas e um parser central, e faça com que a UI receba inputs limpos.
Use uma tabela de rotas compartilhada
Faça iOS e Android concordarem numa lista única e legível de rotas. Pense nisso como um contrato.
Cada rota mapeia para:
- uma tela, e
- um pequeno modelo de input.
Por exemplo, “Detalhe do pedido” mapeia para uma tela Order com um input como OrderRouteInput(id). Se uma rota precisa de valores extras (como uma origem de referência), eles pertencem a esse modelo de input, não espalhados pelo código da view.
Centralize parsing e validação
Mantenha parsing, decodificação e validação num único lugar. A UI não deveria ficar perguntando “Esse token está presente?” ou “Esse ID é válido?” Ela deveria receber ou um input de rota válido ou um estado de erro claro.
Um fluxo prático:
- Receber a URL (toque, scan, share sheet)
- Parseá-la numa rota conhecida
- Validar campos requeridos e formatos permitidos
- Produzir um destino de tela mais um modelo de input
- Navegar por um único ponto de entrada
Adicione uma tela de fallback para “link desconhecido”. Faça-a útil, não um beco sem saída: mostre o que não pôde ser aberto, explique em linguagem simples e ofereça ações como Ir para Home, Buscar ou Fazer login.
Passo a passo: desenhe deep links e comportamento de “abrir no app”
Bons deep links são monótonos do melhor jeito. Pessoas tocam e chegam na tela certa, esteja o app instalado ou não.
Passo 1: escolha os pontos de entrada que importam
Liste os 10 primeiros tipos de link que as pessoas realmente usam: convites, redefinição de senha, recibos de pedido, “ver ticket”, links promocionais. Mantenha propositalmente pequeno.
Passo 2: escreva os padrões como um contrato
Para cada ponto de entrada, defina um padrão canônico e os dados mínimos necessários para abrir a tela certa. Prefira IDs estáveis em vez de nomes. Decida o que é obrigatório vs opcional.
Regras úteis:
- Um propósito por rota (convite, reset, recibo).
- Parâmetros obrigatórios estão sempre presentes; os opcionais têm defaults seguros.
- Use os mesmos padrões em iOS (SwiftUI) e Android (Kotlin).
- Se espera mudanças, reserve uma versão simples (como v1).
- Defina o que acontece quando parâmetros faltam (mostrar tela de erro, não uma página em branco).
Passo 3: decida comportamento de login e o destino pós-login
Escreva, por tipo de link, se o login é necessário. Se for, lembre-se do destino e continue após o login.
Exemplo: um link de recibo pode mostrar uma prévia sem login, mas tocar em “Baixar invoice” pode exigir login e deve retornar o usuário exatamente para aquele recibo.
Passo 4: defina regras de handoff de token (mantenha segredos fora das URLs)
Se o link precisa de um token one-time (aceitação de convite, reset, sign-in mágico), defina quanto tempo ele fica válido e como pode ser usado.
A abordagem prática: a URL carrega um código de uso único e curto, e o app o troca com seu backend por uma sessão real.
Passo 5: teste os três estados do mundo real
Deep links quebram nas bordas. Teste cada tipo de link nestas situações:
- Cold start (app fechado)
- Warm start (app em memória)
- App não instalado (o link ainda cai num lugar sensato)
Se você mantiver rotas, checagens de auth e regras de troca de token num só lugar, evita espalhar lógica customizada por telas Kotlin e SwiftUI.
Erros comuns que quebram deep links (e como evitá-los)
Deep links costumam falhar por razões prosaicas: uma suposição pequena, uma tela renomeada ou um token “temporário” que acaba aparecendo em todo lugar.
Falhas que você verá por aí (e correções)
-
Colocar tokens de acesso na URL (e vazar nos logs). Query strings são copiadas, compartilhadas, guardadas no histórico e capturadas por analytics e logs de crash. Correção: coloque apenas um código one-time no link, resgate-o dentro do app e expire rápido.
-
Assumir que o app está instalado (sem fallback). Se um link leva a uma página de erro ou não faz nada, usuários desistem. Correção: forneça uma página web fallback que explique o que acontecerá e ofereça um caminho de instalação. Mesmo um simples “Abra o app para continuar” é melhor que silêncio.
-
Não tratar múltiplas contas num mesmo dispositivo. Abrir a tela certa para o usuário errado é pior que um link quebrado. Correção: ao receber um link, cheque qual conta está ativa, peça confirmação ou troca, e então continue. Se a ação requer um workspace específico, inclua um identificador de workspace (não um segredo) e valide.
-
Quebrar links quando telas ou rotas mudam. Se sua rota está atrelada a nomes de UI, links antigos morrem ao renomear uma aba. Correção: desenhe rotas estáveis por intenção (invite, ticket, order) e mantenha versões antigas compatíveis.
-
Sem rastreabilidade quando algo dá errado. Sem como reproduzir o que aconteceu, o suporte só pode chutar. Correção: inclua um request ID não sensível no link, logue no servidor e no app e mostre uma mensagem de erro que inclua esse ID.
Um cheque rápido de realidade: imagine um link de convite enviado num chat em grupo. Alguém abre num telefone do trabalho com duas contas, o app não está instalado no tablet, e o link é reenviado a um colega. Se o link contém só um código de convite, suporta fallback, pede pela conta certa e loga um request ID, esse link simples pode ter sucesso em todas essas situações sem expor segredos.
Exemplo: um link de convite que abre a tela certa sempre
Convites são um caso clássico: alguém manda o link num mensageiro e o receptor espera tocar uma vez e chegar na tela de convite, não numa home genérica.
Cenário: um gerente convida um novo agente de suporte para entrar no workspace “Support Team”. O agente toca o convite no Telegram.
Se o app está instalado, o sistema deve abrir o app e passar os detalhes do convite para ele. Se o app não está instalado, o usuário deve cair numa página web simples que explique o que é o convite e ofereça o caminho de instalação. Após instalar e abrir pela primeira vez, o app ainda deve conseguir completar o fluxo do convite para que o usuário não precise procurar o link de novo.
Dentro do app, o fluxo é o mesmo em Kotlin e SwiftUI:
- Ler o código do convite do link recebido.
- Checar se o usuário está logado.
- Verificar o convite no backend e então rotear para a tela correta.
A verificação é o ponto-chave. O link não deve conter segredos como um token de sessão de longa duração. Ele deve transportar um código curto que só vale depois que seu servidor o valida.
A experiência do usuário deve ser previsível:
- Não logado: verá uma tela de login e então retornará para aceitação do convite após o login.
- Logado: verá uma confirmação “Entrar no workspace” e então cairá no workspace correto.
Se o convite expirou ou já foi usado, não jogue o usuário numa página de erro vazia. Mostre uma mensagem clara e um próximo passo: solicitar novo convite, trocar de conta ou contactar um admin. “Este convite já foi aceito” é melhor que “Token inválido.”
Checklist rápido e próximos passos
Deep links só parecem “prontos” quando se comportam do mesmo jeito em todos os lugares: cold start, warm start e quando o usuário já está logado.
Checklist rápido
Antes de lançar, teste cada item em dispositivos reais e versões do SO:
- O link abre a tela correta em cold start e warm start.
- Nada sensível está na URL. Se precisar passar um token, que seja de curta duração e preferencialmente de uso único.
- Links desconhecidos, expirados ou já usados caem num tela clara com mensagem útil e próximo passo seguro.
- Funciona a partir de apps de email, navegadores, scanners de QR e prévias de mensageiros (alguns pré-abrem links).
- O logging te diz o que aconteceu (link recebido, rota parseada, auth requerida, sucesso ou motivo de falha).
Uma forma simples de validar é escolher um punhado de links essenciais (convite, reset de senha, detalhe de pedido, ticket de suporte, promoção) e rodá-los pelo mesmo fluxo de teste: tocar de um email, tocar de um chat, escanear um QR, abrir após reinstalar.
Próximos passos (mantenha sustentável)
Se deep links começam a se espalhar por telas, trate roteamento e auth como canos compartilhados, não código por tela. Centralize parsing de rotas num lugar e faça cada destino aceitar parâmetros limpos (não URLs brutas). Faça o mesmo para auth: um único portão que decide “continuar agora” vs “entrar primeiro e depois continuar”.
Se quiser reduzir glue code personalizado, pode ajudar construir backend, auth e apps móveis juntos. AppMaster (appmaster.io) é uma plataforma no-code que gera backends e apps nativos prontos para produção, o que pode facilitar manter nomes de rota e endpoints de resgate de códigos alinhados conforme os requisitos mudam.
Se fizer só uma coisa na próxima semana, faça isto: escreva suas rotas canônicas e o comportamento de fallback exato para cada caso de falha, e então implemente essas regras numa camada única de roteamento.
FAQ
Um deep link deve abrir exatamente a tela que o link indica, não uma tela genérica ou o dashboard. Se o app não estiver instalado, o link ainda deve ajudar: levar a um lugar sensato e orientar o usuário a voltar ao mesmo destino após a instalação.
Universal Links (iOS) e App Links (Android) usam URLs web normais e podem abrir o app quando ele está instalado, com um fallback elegante quando não está. Schemes customizados são mais fáceis de configurar, mas podem ser bloqueados ou tratados de forma inconsistente por navegadores e outros apps, então funcionam melhor como opção secundária.
Muitos apps de email e mensageiros abrem links dentro de um navegador embutido que pode não repassar para o sistema da mesma forma que Safari ou Chrome. Planeje um passo extra deixando o fallback web claro e tratando os casos em que o usuário cai primeiro numa página web.
No cold start o app pode exibir splash, rodar checagens de inicialização ou carregar config antes de estar pronto para navegar. A correção confiável é armazenar o destino do link imediatamente, terminar a inicialização e então “reproduzir” a navegação quando o app estiver pronto.
Não coloque tokens de acesso de longa duração, refresh tokens, senhas ou dados pessoais na URL: URLs são logadas, compartilhadas e cacheadas. Use um código de uso único e curto no link e troque-o com seu backend depois que o app abrir.
Faça o parse do link, armazene o destino pretendido e então roteie com base no estado de autenticação para que o login ocorra uma vez e o usuário chegue à tela correta. Mantenha o “return target” pequeno, com expiração curta, e limpe-o após o uso.
Trate rotas como um contrato compartilhado e centralize parsing e validação em um único lugar, passando inputs limpos para as telas em vez de URLs brutas. Isso evita que cada tela invente suas próprias regras sobre parâmetros opcionais, IDs faltantes e tratamento de erros.
Verifique qual conta está ativa e se ela corresponde ao workspace ou tenant indicado pelo link; peça ao usuário para confirmar ou trocar antes de mostrar conteúdo privado. Um passo curto de confirmação é melhor do que abrir a tela certa sob a conta errada.
Abra a tela estável mais próxima, como uma lista, e mostre uma mensagem curta explicando o que não pôde ser aberto. Evite telas em branco, falhas silenciosas ou redirecionar para uma tela de login sem contexto.
Teste cada link importante em três estados: app fechado, app já em execução e app não instalado — e faça isso a partir de fontes reais como email, apps de chat e leitores de QR. Se você usa AppMaster (appmaster.io), manter nomes de rota e endpoints de resgate de código alinhados entre backend e apps nativos reduz a quantidade de glue code personalizado que precisa ser mantida.


