Pular para o conteúdo

Token assinado, allowlist, eventos

Aula 4 de 68 minOperarAtualizada em 2026-10-04

Vídeo em produção

A gravação desta aula está no lote de produção. O objetivo, o exercício e a documentação já valem. Duração-alvo: 8 min.

Objetivo: Incorporar painel com token

Selo de estado: Preview (teto atual do produto) · Curso ACD-250 — Integrações e consumo externo · Aula 4 de 6 · Atualizado em 2026-10-04.

Objetivo#

Ao final desta aula você vai incorporar painel com token — assinado no seu servidor, escopado a um painel, com a origem na allowlist e os eventos do SDK conectados.

Vídeo#

Identificador no manifesto: acd-250-04-embed-sdk · duração-alvo 8 min · tela do produto: /connect.

Roteiro de gravação (6 capítulos):

#Minutagem-alvoCapítuloTela do produto
10:00–0:45Abertura: título + selo. As três peças — chave de embed, token no seu servidor, embed-sdk.js no navegador./dashboards
20:45–2:15Ligar o embed em Compartilhar; copiar o segredo (uma vez só) e guardá-lo como variável de ambiente./dashboards
32:15–4:00Assinar o token HMAC-SHA256 no servidor: claims, exp de 15 min, escopo de um painel, recorte de linha opcional.editor de código
44:00–5:30Montar o <iframe> e conectar o SDK: onPronto, onEstado, onInteracao, onErro; comandos trocarPagina e aplicarFiltros.página hospedeira local
55:30–6:45Allowlist de origem (global por ambiente hoje) e o que acontece quando a origem não está nela./dashboards
66:45–8:00Rotacionar segredo, desativar embed, despublicar. Erros comuns e encerramento./dashboards

Conteúdo#

Embed é colocar um painel publicado do ingestia.io dentro de outro site — o portal de um cliente, um sistema interno — por meio de um <iframe>, com um token assinado controlando quem vê o quê e uma allowlist de origem controlando quem pode emoldurar o iframe. É o espírito do Power BI Embedded, sem custo de capacidade: você paga apenas o processamento das consultas do próprio painel, como em qualquer BI nativo.

Diferente das aulas 2, 3 e 6, aqui a credencial não é a chave de API. Embed tem a sua própria: uma chave de embed, com keyId público e um secret que só o seu servidor conhece.

Três peças, três lugares

PeçaOnde rodaO que faz
Chave de embed (keyId + secret)ingestia.io (UI)nasce ao ligar o embed; o segredo aparece uma vez só
Tokeno seu servidorprova assinada de que este usuário vê este painel por N minutos
embed-sdk.jsnavegador do seu usuáriocria o iframe, entrega eventos, aceita comandos

Na tela

A configuração vive em Dashboards → (painel) → Compartilhar → Embed. O botão Ligar o embed gera a chave e mostra o segredo uma única vez: copie e guarde como variável de ambiente no seu servidor (por exemplo INGESTIA_EMBED_SECRET) — ele não volta a aparecer. Na mesma tela você escolhe o teto de validade do token (de 5 min a 12 h; recomendado 15 min) e tem o botão Gerar link de teste, que vale ouro: dá para validar o iframe antes de escrever qualquer linha de código.

Vale notar que /connect e /excel, as telas das aulas 2 e 3, não participam daqui — são o canal de dados; embed é o canal de painel. A única coisa que as duas famílias compartilham é a regra de ouro: segredo nunca no navegador.

Assinar no servidor (nunca no navegador)

js
// Node.js — RODE NO SEU SERVIDOR. O segredo nunca vai ao navegador.
import { createHmac, randomBytes } from "node:crypto";
const SEGREDO = process.env.INGESTIA_EMBED_SECRET;
const agora = Math.floor(Date.now() / 1000);
const claims = {
  k: "SEU_KEY_ID", w: "SEU_WORKSPACE_ID", d: "SEU_DASHBOARD_ID",
  iat: agora,
  exp: agora + 900,                          // 15 min
  n: randomBytes(16).toString("base64url"),  // nonce
  // f: { c: "filial", v: ["<id da filial deste visitante>"] }, // recorte de linha (opcional)
};
const corpo = "v1." + claims.k + "." + Buffer.from(JSON.stringify(claims)).toString("base64url");
const token = corpo + "." + createHmac("sha256", SEGREDO).update(corpo).digest("base64url");
const urlDoEmbed = `https://ingestia.io/d/embed/${token}`;

Três propriedades do token merecem atenção. Ele é HMAC-SHA256, conferido em tempo constante antes de qualquer outra regra. Ele abre um painel — o do dashboardId assinado, nunca outro. E ele não é eterno: se exp - iat passar do teto da chave, o painel recusa; o teto absoluto é 12 h.

A claim f é o recorte de linha opcional e tem uma regra que não se negocia: ela só aperta o que a política do painel já permite — nunca alarga. E PII fica sempre mascarada.

Montar o iframe e ouvir os eventos

html
<div id="painel"></div>
<script src="https://ingestia.io/embed-sdk.js"></script>
<script>
  var painel = IngestiaEmbed.criar({
    container: "#painel",
    url: urlDoEmbedVindaDoSeuServidor, // /d/embed/<token>
    autoAltura: true,
    onPronto: function (p) { /* { dashboardId, pagina } — token aceito, painel renderizou */ },
    onEstado: function (e) { /* { f, filtros } — só estado, nunca linhas */ },
    onInteracao: function (i) { /* { tipo: "pagina" | "drill", resumo } */ },
    onErro: function (err) { /* { codigo } — token expirado: emita outro */ },
  });
  // Comandos: painel.trocarPagina(1); painel.aplicarFiltros({ … }); painel.destruir();
</script>

O canal postMessage é fail-closed: mensagem fora do formato é recusada, a ponte fixa a primeira origem válida do handshake e só aceita comandos dela, e as respostas usam targetOrigin explícito, nunca *. E ele nunca transporta linha de dado, token ou segredo — apenas o estado de filtros, a mesma informação que já apareceria no ?f= da URL do iframe.

Um detalhe operacional que economiza horas: aplicarFiltros com estado não-vazio recarrega o iframe (o pipeline lê o ?f= na montagem), então aplique-o dentro ou depois de onPronto. trocarPagina e aplicarFiltros(null) não recarregam.

Exemplo: a Aurora Varejo no portal dos lojistas

A Aurora tem um portal fictício para seus lojistas em https://portal.example.com. Para cada lojista autenticado nesse portal, o backend da Aurora assina um token com f: { c: "filial", v: ["<id da filial daquele lojista>"] } e monta o iframe apontando para ele. O painel "Aurora — Vendas por filial" aparece dentro do portal, e cada lojista vê apenas a própria filial — o recorte vem do token, não de uma configuração por cliente. Como um token novo é emitido a cada carregamento de página, a validade de 15 minutos nunca incomoda quem está navegando.

SuperfícieEstadoQuando usar
Protocolo do SDK (postMessage v1, fail-closed)GA-candidatoos testes são a especificação: handshake, recusa de mensagem fora de formato, fixação de origem
Embed SDK como superfície pública (token + allowlist)Previewpainel dentro de portal próprio — é o uso certo hoje
Allowlist de origem por workspaceRoadmapnão existe: hoje a allowlist é global por ambiente
Eventos de cross-filter (clique em barra/fatia)Roadmapnão são emitidos; use onInteracao e onEstado

Peça a allowlist com antecedência

A allowlist de origem é global por ambiente hoje, configurada por fora do workspace. Sem o seu domínio nela, o iframe carrega em branco e o onErro dispara com origem_nao_autorizada. Peça a inclusão antes da data de ir ao ar, nunca na hora.

Erros comuns

SintomaCausa provávelO que fazer
Tela de recusa ou onErro logo ao abrirtoken expirado, ou origem fora da allowlistemita token novo; peça a inclusão do domínio
Segredo no código do navegadorassinatura foi feita no clienteassine no servidor; rotacione o segredo imediatamente
Filtros não aplicamaplicarFiltros chamado antes do onProntoaplique dentro ou depois de onPronto
filtros_invalidosestrutura de filtro inválidause os ids do editor; leia o onEstado para descobri-los
Todo token parou de valer de uma vezo segredo foi rotacionadoatualize a variável de ambiente no servidor
O painel sumiu do portalo painel de origem foi despublicadorepublique — token nunca abre painel despublicado
Prometeram ao cliente latência de carregamentorenderização e latência em produção não são medidasnão prometa número; embed é Preview, sem SLA

Estado

O protocolo do SDK (postMessage v1, fail-closed) é GA-candidato — os testes automatizados funcionam como especificação do embed-sdk.js. O que mantém a aula em Preview são duas coisas concretas: a allowlist de origem é global por ambiente, não por workspace (evolução prevista), e a renderização e a latência em produção não são medidas hoje. Eventos de cross-filter e allowlist por workspace são Roadmap. Embed não tem custo de capacidade; o custo é o das consultas do próprio painel.

Faça você mesmo#

No workspace de treino Aurora Varejo, como dona (comece em /dashboards):

  1. Num painel publicado (por exemplo "Aurora — Vendas"), abra Compartilhar e clique Ligar o embed. Escolha validade de 15 min.
  2. Copie o segredo exibido e simule guardá-lo como variável de ambiente (INGESTIA_EMBED_SECRET) — releia o aviso de que ele não volta a aparecer.
  3. Use Gerar link de teste e abra o link numa aba. Confirme que o painel carrega antes de escrever qualquer código.
  4. Monte localmente uma página HTML simples com um <iframe> apontando para esse link de teste.
  5. Inclua o embed-sdk.js e assine onPronto e onErro. Confirme que onPronto dispara com { dashboardId, pagina }.
  6. Chame aplicarFiltros antes do onPronto e observe o filtro não pegar; mova a chamada para dentro do onPronto e confirme que agora aplica (e que o iframe recarrega).
  7. Abra o mesmo link a partir de uma origem que você sabe que não está na allowlist e confirme o código origem_nao_autorizada no onErro.
  8. Use gerar segredo novo para rotacionar a chave e confirme que o link de teste anterior deixa de funcionar.

Você terminou quando tiver um iframe local carregando o painel de treino com o SDK conectado, tiver visto origem_nao_autorizada com os próprios olhos — e conseguir explicar por que o segredo nunca pode estar no código do navegador.

Checagem rápida#

Três perguntas no final da aula, corrigidas no servidor. A aula só conta como concluída depois da checagem.

Documentação relacionada#

Capacidades ensinadas#

C12.5 — o selo exibido na aula é sempre o estado mais conservador entre as capacidades citadas; nada aqui é "GA".

Carregando seu progresso…