Pendo
Pendo é ferramenta de product analytics + in-app guides / tours / NPS. Não substitui pixel de conversão — mas, diferente do que a versão anterior desta página dizia, o Pendo também recebe eventos de conversão do front (ver "Eventos" abaixo).
Configuração por brand
// overrides/<brand>/app/config/analytics/pendo.ts
import type { PendoConfig } from "~/types/pendo";
export const pendoConfig: PendoConfig = {
enabled: true,
token: "479456b8-d797-4b4a-a03c-b3790265ef4a",
accountId: "cl-bet7k-com",
};
export type { PendoConfig } from "~/types/pendo";
| Campo | Tipo | Pra quê serve |
|---|---|---|
enabled | boolean | Master switch. false → o hook inteiro é no-op |
token | string? | Agent token do Pendo. Vai no src do script: https://cdn.pendo.io/agent/static/<token>/pendo.js |
accountId | string? | Identificador de conta anexado a toda sessão (account.id) |
:::danger Não existe apiKey
A versão anterior desta página documentava pendoConfig = { enabled, apiKey }. apiKey não existe em PendoConfig (app/types/pendo.ts) — o campo do token chama-se token. Config com apiKey é silenciosamente ignorada e o Pendo não carrega (o hook exige enabled && token && accountId).
:::
Default do base: { enabled: false }. Brands opt-in por override.
Brands (estado real)
| Brand | enabled | Observação |
|---|---|---|
cl-bet7k-com | ✅ true | ligado por decisão de produto |
ng-7k-bet | ✅ true | ligado por decisão de produto |
7k-bet-br | ❌ false | desligado por decisão de produto (2026-07-16); token/accountId preservados |
betpontobet-bet-br | ❌ false | idem |
| demais brands | ❌ false | herdam o default do base (sem override) |
As quatro brands com override compartilham o mesmo agent token; o que separa as contas é o accountId.
Religar uma brand desligada = voltar enabled pra true (a config já está lá).
Como carrega — lane exclusiva de interação
Pendo não está na lane deferred junto com Hotjar/RA. Ele tem a sua própria: scheduleThirdPartyOnInteraction, que só libera após a primeira interação real do visitante.
Racional (de AnalyticsLoaders.tsx): Pendo só importa pra usuário engajado. É um deferral honesto, não truque de lab — vale igual pra todo mundo: um visitante real que entra e sai sem interagir também nunca carrega Pendo, e uma auditoria sintética (que nunca interage) se comporta exatamente como essa visita não-engajada. Sem branch específico de bot.
Consequência operacional: se você abre a página e não clica em nada, window.pendo não vai existir. Isso é esperado.
O loader instala um stub com fila (pendo._q) antes do script chegar, então chamadas antecipadas não se perdem.
Identificação do visitante
pendo.initialize({
visitor: { id: <visitorId>, user_id: <numeric id> },
account: { id: pendoConfig.accountId },
autoFrameInstall: true,
frameIdentitySync: true,
})
| Campo | Valor |
|---|---|
visitor.id | o cactus_id do usuário — string enviada pelo BFF em /auth/user-profile, distinta do AuthUser.id numérico. É o identificador canônico cross-stack usado pelas integrações de terceiro |
visitor.id (fallback) | quando o usuário autenticado não tem cactus_id — ele é gerado só após KYC, e algumas contas não têm (ex: Nigeria) — cai pro AuthUser.id numérico stringificado, pra que o usuário logado ainda seja identificado em vez de rastreado como anônimo |
visitor.user_id | sempre o AuthUser.id numérico stringificado quando autenticado. Vai em toda interação coletada (Features no-code e pendo.track()), pra segmento/relatório do lado do Pendo |
account.id | pendoConfig.accountId |
Visita não autenticada manda visitor.id vazio → Pendo rastreia anonimamente.
Em transição de auth (login/logout) o hook re-identifica: prefere pendo.identify() quando o agent expõe, e cai pro initialize() quando não.
Eventos
Seis eventos chegam ao Pendo, por dois caminhos diferentes.
Manuais (emitidos pelo usePendo)
O agent no-code do Pendo não consegue observar estes, então o hook os emite explicitamente via pendo.track():
| Evento | Quando |
|---|---|
app_loaded | uma vez por carregamento de documento, logo após o auth hidratar. Cobre primeira visita, F5/reload e navegação externa de volta pro site. Não dispara em navegação SPA (mesmo documento). Params: authenticated, visitor_id |
login | quando isAuthenticated transiciona false → true. Param: visitor_id |
logout | quando isAuthenticated transiciona true → false |
Via matriz de destinos
Três eventos de conversão roteados pelo canal pendo de app/analytics/destinations.ts:
| Evento do front | Track Event no Pendo |
|---|---|
sign_up | sign_up |
deposit_confirmed | deposit_confirmed |
withdraw | withdraw |
:::note login está fora da matriz de propósito
login é emitido reativamente pelo usePendo (na transição de auth). Roteá-lo também pela matriz duplicaria a contagem. É por isso que o slot pendo de login é null em destinations.ts — não é esquecimento.
:::
O adapter no-opa quando a brand tem Pendo desligado ou o agent ainda não está pronto.
O que Pendo entrega
- Tours interativos pra novo usuário (highlight de features)
- Guias contextuais
- NPS surveys agendadas
- Analytics de uso de feature (Features no-code)
Configuração de cada tour é feita no painel Pendo (https://app.pendo.io), não no código.
Cookie
Pendo escreve cookies próprios pendo_* (formato interno). TTL ~1 ano. Categoria LGPD: outros / marketing.
Como debuggar
window.pendo // SDK (só existe DEPOIS da 1ª interação)
window.pendo._q // fila do stub, antes do agent carregar
window.__pendoInitialized // guard de initialize único
Painel: https://app.pendo.io. Network → filtra pendo.io.
Se window.pendo estiver undefined: (1) interagiu com a página?, (2) a brand tem enabled: true?, (3) token e accountId estão preenchidos?
Anti-patterns
- Usar
apiKeyno config. O campo étoken. O hook exigeenabled && token && accountId— semtokenele é no-op silencioso. - Tour disparado em todo user, sempre. Anti-UX. Use targeting no painel (só novos users, ou só quem passou por X tela).
- Rotear
loginpela matriz de destinos. Duplica com o evento reativo do hook. - Concluir que Pendo está quebrado porque
window.pendonão existe no load. A lane é interaction-only por design. - Confundir NPS do Pendo com NPS do Hotjar. Use um só pra evitar fadiga de pesquisa (hoje o Hotjar está desligado em todas as brands — ver Hotjar).