Pular para o conteúdo

Visão geral ​

Uma mensagem in-app é um card ou uma barra que um SDK exibe dentro do app, enquanto ele está aberto, em um de quatro layouts. O painel (ou a API REST de administração) cria a mensagem e sua segmentação; o SDK apenas pergunta ao servidor o que pode exibir e reporta o que aconteceu.

A entrega é dirigida pelo servidor e a renderização é feita no cliente:

  • O servidor decide a elegibilidade (agendamento, plataforma, segmento e frequência). O SDK nunca avalia regras de segmentação por conta própria.
  • O SDK renderiza o payload retornado com componentes nativos e reporta eventos de engajamento. O servidor os contabiliza.

Esta página é o contrato entre o servidor e qualquer SDK (React Native, iOS, Android ou uma integração própria). As duas rotas de dispositivo estão na Referência da API In App; esta página acrescenta as regras que a referência não consegue expressar.

As duas rotas autenticam com a chave de cliente do App (Authorization: Bearer ck_..., a mesma chave usada no registro de dispositivo) e comprovam a posse do dispositivo com o push token do dispositivo no corpo da requisição. Uma chave REST é rejeitada com 403, e uma chave ausente ou inválida com 401.

Fluxo da sessão ​

  1. Início da sessão. Quando o SDK estiver inicializado e o dispositivo registrado (ou seja, houver um device_id), chame eligible. Não chame a cada tela: a rota tem limite de requisições por dispositivo (veja Limites).
  2. Decida o que exibir. eligible retorna até 5 mensagens com gatilho session_start e até 10 com gatilho event, da mais antiga para a mais nova dentro de cada grupo (veja Gatilhos).
    • Exiba imediatamente a primeira mensagem de início de sessão que o SDK conseguir renderizar.
    • Mantenha as mensagens de evento em memória e exiba-as quando o app reportar o evento correspondente.
    • No máximo uma mensagem fica visível por vez, e cada mensagem é exibida no máximo uma vez por sessão.
  3. Reporte a impressão uma vez por exibição, não por renderização. Envie uma impression quando a mensagem ficar visível para o usuário. Uma nova renderização, uma rotação ou um novo layout da mesma exibição não deve enviar outra. Envie a impressão antes de qualquer click ou dismiss dessa exibição.
  4. Reporte a interação.
    • O toque em um botão envia click com o button_id desse botão.
    • O toque na imagem, quando a mensagem tem image_action, executa essa ação e envia click com button_id: "image".
    • Em uma mensagem top ou bottom, o toque no corpo executa a ação do primeiro botão e reporta um click com o id desse botão.
    • Fechar a mensagem envia dismiss com um reason: close_button, swipe ou outside_tap quando o usuário a fechou, auto quando o temporizador a fechou. Um dismiss só faz sentido após uma impressão.
    • Quando um clique fecha a mensagem, reporte o clique; um dismiss reportado depois de um clique é ignorado pela taxa de dispensa.

Buscando mensagens elegíveis ​

http
POST /v1/apps/{app_id}/in-app-messages/eligible
Authorization: Bearer ck_example
Content-Type: application/json
json
{
  "device_id": "00000000-0000-4000-8000-000000000001",
  "token": "example-push-token"
}

Os dois campos são obrigatórios. Campos desconhecidos são rejeitados com 422 invalid_body.

Resposta 200:

json
{
  "messages": [
    {
      "id": "00000000-0000-4000-8000-0000000000a1",
      "format": "center",
      "title": "Example title",
      "body": "Example body text.",
      "image_url": "https://cdn.example.com/example.png",
      "image_action": { "type": "url", "value": "https://example.com/offer" },
      "style": {
        "background_color": "#102030",
        "text_color": "#FFFFFF",
        "button_color": "#E94B8B",
        "button_text_color": "#FFFFFF",
        "text_align": "center",
        "margin": true
      },
      "dismiss_after_seconds": 8,
      "trigger": { "type": "event", "name": "checkout_done" },
      "buttons": [
        {
          "id": "a1b2c3d4",
          "label": "See more",
          "action": { "type": "deeplink", "value": "exampleapp://offers/42" }
        },
        {
          "id": "e5f6a7b8",
          "label": "Docs",
          "action": { "type": "url", "value": "https://example.com/docs" }
        }
      ]
    }
  ]
}

messages é sempre um array; fica vazio quando nada é elegível, e também quando o App está desativado.

Payload ​

CampoTipoNotas
idstring (UUID)Id da mensagem; vai no path da rota events.
formattop, center, bottom ou fullDefine o layout. Um SDK que não conhece um formato renderiza center.
titlestringAté 100 caracteres. Pode estar vazio quando body está preenchido.
bodystringAté 500 caracteres, pode conter quebras de linha. Pode estar vazio quando title está preenchido.
image_urlstringAusente quando a mensagem não tem imagem. Sempre https.
image_actionobjectAusente quando a imagem não é tocável. Mesmo formato da ação de um botão: type (url ou deeplink) e value. Veja Ação da imagem.
styleobject ou nullnull significa os padrões do SDK. Veja Estilo.
dismiss_after_secondsinteger ou nullnull mantém a mensagem até o usuário fechá-la. Veja Fechamento automático.
triggerobjectSempre presente. {"type": "session_start"} ou {"type": "event", "name": "..."}. Veja Gatilhos.
buttonsarrayDe 0 a 2 botões, na ordem de exibição.
buttons[].idstringOpaco, atribuído pelo servidor e estável entre edições da mensagem. Envie-o de volta como button_id.
buttons[].labelstringAté 30 caracteres.
buttons[].action.typeurl, deeplink ou push_permissionurl é sempre https; deeplink é https ou um scheme customizado do app. push_permission é uma ação integrada do SDK, veja Ação de permissão de push.
buttons[].action.valuestringO link a abrir. Ausente em push_permission: a chave não é enviada, nem como null.

A segmentação (segmento, plataformas, agendamento, frequência) nunca faz parte do payload.

Layouts ​

A tabela de layouts é o contrato. Os tamanhos são limites, não especificações em pixels: cada plataforma renderiza com seus próprios componentes, mas deve ficar dentro dos limites e oferecer os gestos de fechamento listados aqui.

formatPosicionamentoFundo (backdrop)Gestos de fechamentoImagem
topBarra abaixo da área segura superior, no máximo 40% da altura da telanenhumclose_button, swipe para cimaminiatura pequena à esquerda
bottomBarra acima da área segura inferior, no máximo 40% da altura da telanenhumclose_button, swipe para baixominiatura pequena à esquerda
centerCard centralizado, no máximo 90% da largura50% pretoclose_button, outside_tapacima do texto
fullDe ponta a ponta dentro das áreas seguras, botões fixos na parte inferiornão se aplicasomente close_buttonacima do texto
  • Um controle de fechar é sempre exibido, qualquer que seja o layout e o temporizador.
  • style.margin: true aplica uma margem de 16 pontos às bordas da tela (top, bottom e full); false remove a margem.
  • Com dois botões, top e bottom os posicionam lado a lado e os quebram em coluna quando não cabem.
  • Em uma mensagem top ou bottom, o toque no corpo executa a ação do primeiro botão e reporta um click com o id desse botão.
  • Todo fechamento reporta o reason do gesto que foi usado.

Estilo ​

style traz tokens opcionais de aparência. Um token ausente (ou style: null) significa o padrão do SDK.

TokenValoresAplica-se a
background_color#RRGGBBO fundo do card ou da barra.
text_color#RRGGBBTítulo e corpo.
button_color#RRGGBBFundo do botão.
button_text_color#RRGGBBRótulo do botão.
text_alignleft, center ou rightTítulo e corpo. Ausente significa left.
marginbooleanVeja Layouts. Ausente significa true.

Quando button_color ou button_text_color está definido, todos os botões o usam (o que estiver ausente mantém o padrão do SDK), de modo que o botão secundário deixa de ser desenhado com um estilo diferente.

As cores são #RRGGBB sem alpha, em maiúsculas, para que as duas plataformas renderizem o mesmo valor. O SDK deve ignorar uma cor que não consiga interpretar e usar o padrão daquele token em vez de falhar. O servidor não verifica contraste; o painel apenas avisa o autor.

Ação da imagem ​

Quando image_action está presente, o toque na imagem a executa e reporta click com button_id: "image". Sem image_action, a imagem não é tocável. A ação segue as mesmas regras e verificações de link da ação de um botão (veja Texto simples e links).

Fechamento automático ​

Quando dismiss_after_seconds está definido (de 1 a 300), o SDK fecha a mensagem essa quantidade de segundos depois de ela ter ficado visível e reporta dismiss com reason: "auto". A contagem regressiva pausa enquanto o app está em segundo plano. O controle de fechar continua disponível durante a contagem, e o usuário ainda pode fechar a mensagem antes, o que reporta o reason do próprio gesto.

Gatilhos ​

A busca continua no início da sessão: uma chamada a eligible por sessão.

  • {"type": "session_start"}: exibir a mensagem no início da sessão (a primeira renderizável, veja Fluxo da sessão).
  • {"type": "event", "name": "checkout_done"}: manter a mensagem em memória e exibi-la quando o app reportar um evento com esse nome exato por meio do trackEvent(name) do SDK. O SDK exibe a mensagem retida mais antiga cujo gatilho nomeia o evento, somente quando nenhuma mensagem está visível, e cada mensagem no máximo uma vez por sessão.

Os nomes de evento são comparados localmente: o SDK nunca os envia ao servidor e descarta as mensagens retidas quando a sessão termina. O servidor não mantém um registro de nomes de evento; o painel sugere os nomes já usados pelas mensagens do App.

A elegibilidade (público, plataforma, agendamento e frequência) é decidida pelo servidor no momento da busca para os dois tipos, e a impressão é reportada quando a mensagem é de fato exibida.

Compatibilidade ​

  • O SDK deve ignorar campos do payload que não conhece.
  • Para um format desconhecido, renderize center.
  • Mudanças neste payload após a primeira versão do SDK são somente aditivas.

Ação de permissão de push ​

Um botão pode carregar {"type": "push_permission"}, sem value. É a única ação que o próprio SDK executa, e ela não recebe dados do autor da mensagem: um SDK continua sem nunca abrir um link fornecido pelo autor, apenas entrega as ações url e deeplink ao app. Somente botões a aceitam; image_action continua sendo url ou deeplink.

Ao toque, reporte primeiro o click, feche a mensagem e depois aja sobre a permissão de notificação do sistema operacional conforme ela estiver naquele momento:

EstadoO que o SDK faz
Não decididoExibe o prompt de permissão do sistema.
NegadoAbre as configurações de notificação do app. Não exibe um prompt que o sistema operacional vai suprimir.
Já concedido (mudou após eligible)Nada.
DesconhecidoNada, e não registra em log nada sensível.

No Android abaixo do 13 não há prompt: um dispositivo com as notificações desativadas vai para as configurações. No Android 13 e superior o sistema operacional não distingue "nunca perguntado" de "negado permanentemente", então o SDK lembra que já exibiu o prompt uma vez e abre as configurações nos toques seguintes. Um usuário que negou em definitivo em uma instalação anterior ou por outro caminho pode não ver nada no primeiro toque.

O click listener do app recebe o clique com action: null, e a ação é executada com ou sem listener. A ação não acrescenta dados ao que o SDK já coleta ou envia: permission_status já faz parte do perfil do dispositivo, e o diálogo do sistema é uma escolha do próprio usuário.

  • Renderize title e body como texto simples. Nunca os interprete como HTML ou Markdown, e nunca os passe a uma web view como marcação.
  • O servidor rejeita um link cujo scheme seja http, javascript, data, file, vbscript, blob ou about. Um link aceito é uma URL https com host ou (para deeplink) um scheme customizado do app, como exampleapp://offers/42. Mesmo assim, o SDK deve abrir apenas links que espera, e um deeplink que o app não consiga tratar deve ser ignorado em vez de causar um crash.

Regras de elegibilidade ​

O servidor retorna uma mensagem somente quando todas estas condições valem no momento da requisição:

  • A mensagem está ativa, não foi excluída e está dentro da sua janela: starts_at já passou (quando definido) e ends_at não passou (quando definido).
  • A mensagem segmenta a plataforma do dispositivo (uma lista de plataformas vazia segmenta todas).
  • O dispositivo pertence a pelo menos um dos segmentos incluídos da mensagem (nenhum segmento incluído segmenta todos os dispositivos) e a nenhum dos segmentos excluídos. A permissão de push não é exigida: o In-App não depende dela.
  • A frequência permite outra exibição (abaixo).
  • Uma mensagem com um botão push_permission é exibida somente a um dispositivo que possa usá-lo. Ela fica oculta quando o status de push do dispositivo é ok, e quando o sdk_version do dispositivo está vazio, ilegível ou é mais antigo que o mínimo do SDK que o dispositivo informa em sdk_name. Os mínimos são React Native 0.8.0 e iOS 0.1.0; nenhum outro SDK recebe a mensagem ainda. Um dispositivo que não informa sdk_name é tratado como React Native, então precisa da 0.8.0 ou mais recente. Um dispositivo com status de push off ou invalid continua elegível. Esta regra falha de forma fechada: um SDK, sdk_name ou versão desconhecidos nunca recebem a mensagem.

Edições, ativação e pausa chegam aos dispositivos na próxima chamada a eligible, com um atraso de até cerca de 60 segundos. Edições nas regras de um segmento usado por uma mensagem podem levar até 60 segundos para serem vistas pela elegibilidade.

Frequência ​

O servidor conta os eventos impression do próprio dispositivo para aquela mensagem. Cliques e fechamentos não contam.

TipoA mensagem é elegível enquanto
onceo dispositivo não tem nenhuma impressão dela.
n_timeso dispositivo tem menos de max impressões dela.
intervalo dispositivo não tem nenhuma impressão dela, ou a última tem pelo menos interval_seconds de idade.

Como o servidor é a autoridade, um SDK não deve manter seu próprio estado de "já exibida" para suprimir mensagens, e deve reportar as impressões de forma confiável: uma impressão perdida torna uma mensagem once elegível novamente.

Reportando eventos ​

http
POST /v1/apps/{app_id}/in-app-messages/{id}/events
Authorization: Bearer ck_example
Content-Type: application/json
json
{
  "device_id": "00000000-0000-4000-8000-000000000001",
  "token": "example-push-token",
  "event_id": "00000000-0000-4000-8000-0000000000e1",
  "type": "click",
  "button_id": "a1b2c3d4",
  "occurred_at": "2026-10-17T14:30:05Z"
}
CampoObrigatórioRegras
device_id, tokensimIguais aos de eligible.
event_idsimUm UUID gerado pelo cliente uma vez por evento. Reutilize o mesmo valor ao tentar enviar esse evento novamente.
typesimimpression, click ou dismiss.
button_idpara clickO id de um dos botões da mensagem, ou image para um toque na imagem de uma mensagem que tem image_action. Não permitido nos outros tipos.
reasonpara dismissclose_button, swipe ou outside_tap quando o usuário fechou a mensagem, auto quando foi o temporizador. Não permitido nos outros tipos.
occurred_atnãoRFC3339. Usado somente quando está dentro de 7 dias no passado e 5 minutos no futuro em relação ao horário do servidor; caso contrário, o servidor grava o próprio horário.

Campos desconhecidos são rejeitados com 422 invalid_body.

Idempotência e novas tentativas ​

Os eventos são deduplicados por (message, device, event_id):

  • Primeira entrega: 201 com { "duplicate": false }.
  • Repetição do mesmo event_id: 200 com { "duplicate": true }; nada novo é gravado.

Ambos significam que o evento foi registrado; trate qualquer um como sucesso e remova-o da fila. Gere o event_id quando o evento acontecer, guarde-o junto com o evento enfileirado e nunca o regenere em uma nova tentativa, para que um reenvio após um timeout não conte em duplicidade.

Mensagens pausadas, expiradas e excluídas ainda aceitam eventos, de modo que uma interação com uma mensagem recém-pausada não se perde.

Comportamento offline ​

  • Enfileire cada evento com seu event_id e occurred_at e envie a fila quando a conectividade voltar, do mais antigo para o mais novo.
  • Mantenha o occurred_at real. Após 7 dias o servidor o substitui pelo horário de recebimento, o que move o evento para o dia errado nas métricas e desloca a frequência interval; por isso descarte eventos enfileirados com essa idade em vez de enviá-los tarde.
  • Evite chamar eligible enquanto ainda houver eventos na fila: envie a fila primeiro, caso contrário o servidor pode retornar uma mensagem que o dispositivo já exibiu.

Erros e limites ​

Os corpos de erro usam o envelope padrão, {"error": {"code", "message", "details"}}.

StatusCódigoRotaSignificado e comportamento do SDK
401unauthorizedambasChave de cliente ausente ou inválida. Não tente novamente.
403forbiddenambasA chave não é uma chave de cliente deste App, ou o par device_id/token não confere. O mesmo código cobre um token errado, um dispositivo desconhecido, um dispositivo excluído e um dispositivo de outro App. Em events, um App desativado no painel também responde 403 (eligible responde uma lista vazia). Registre o dispositivo novamente, não tente de novo como está.
404not_foundeventsA mensagem não existe neste App. Descarte o evento.
422invalid_bodyambasJSON malformado, campo desconhecido, device_id ou token ausente, ou occurred_at inválido. Corrija o cliente, não tente novamente.
422invalid_contenteventsO evento é inválido. details.fields mapeia cada campo para um código: event_id: invalid_id, type: invalid_type, button_id: required, not_allowed ou unknown_button (também um clique em image para uma mensagem sem image_action), reason: invalid_reason ou not_allowed. Descarte o evento.
429rate_limitedambasLimite por dispositivo atingido. Aguarde o header Retry-After (segundos) e tente novamente com o mesmo event_id.
5xx-ambasTente novamente com backoff e o mesmo event_id.

Limites por dispositivo, por minuto: 30 em eligible e 120 em events. Esses valores iniciais podem mudar; o header Retry-After é a fonte da verdade. Agrupe seu trabalho: um eligible por sessão fica muito abaixo do limite.

O que as métricas significam ​

O painel e o endpoint de métricas são calculados a partir dos eventos acima, sobre uma janela de dias no fuso horário do App, pelo occurred_at gravado do evento.

MétricaDefiniçãoAlimentada por
ImpressõesContagem de eventos impression.impression
AlcanceDispositivos distintos com pelo menos uma impressão.impression
CliquesContagem de eventos click, também por button_id. Os toques na imagem são a linha image, listada primeiro.click
Clicadores únicosDispositivos distintos com pelo menos um clique.click
CTRClicadores únicos / alcance, em porcentagem com uma casa decimal. null quando o alcance é 0.impression, click
Taxa de dispensaDispositivos que fecharam a mensagem por conta própria (close_button, swipe, outside_tap) e nunca clicaram / alcance, em porcentagem. null quando o alcance é 0. Um timeout não é rejeição e não é contado.impression, click, dismiss
Fechadas automaticamenteDispositivos cuja única saída foi o temporizador (fechamento auto, nenhum fechamento manual, nenhum clique). Informado como contagem, separado da taxa de dispensa.impression, click, dismiss
Média de impressões por dispositivoImpressões / alcance.impression
Dispositivos elegíveisO tamanho do público segmentado (segmento e plataformas) agora. Uma estimativa pontual, não uma contagem histórica; null para uma mensagem excluída.nenhuma
Taxa de alcanceAlcance / dispositivos elegíveis, limitada a 100.impression

Notas para quem desenvolve SDKs:

  • Um clique ou dismiss sem uma impressão do mesmo dispositivo na janela infla os clicadores únicos ou os dispensadores em relação ao alcance. Por isso a impressão deve ser reportada primeiro.
  • Um dispositivo é contado em no máximo um entre CTR, taxa de dispensa e fechadas automaticamente, de modo que os três nunca somam mais de 100%.
  • Os detalhamentos (plataforma, versão do app, país) contam dispositivos com impressão, usando os valores que o servidor leu do dispositivo quando o evento foi recebido.
  • Como o alcance abrange uma janela e os dispositivos elegíveis são um instantâneo, o alcance pode exceder os dispositivos elegíveis.

MAU ​

Os eventos de In-App não contam como atividade para o MAU (usuários ativos mensais): registrar uma impressão, clique ou dismiss não atualiza o last_seen_at do dispositivo, portanto exibir mensagens In-App nunca altera o MAU de um App.