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-alvo | Capítulo | Tela do produto |
|---|---|---|---|
| 1 | 0:00–0:45 | Abertura: título + selo. As três peças — chave de embed, token no seu servidor, embed-sdk.js no navegador. | /dashboards |
| 2 | 0:45–2:15 | Ligar o embed em Compartilhar; copiar o segredo (uma vez só) e guardá-lo como variável de ambiente. | /dashboards |
| 3 | 2:15–4:00 | Assinar o token HMAC-SHA256 no servidor: claims, exp de 15 min, escopo de um painel, recorte de linha opcional. | editor de código |
| 4 | 4:00–5:30 | Montar o <iframe> e conectar o SDK: onPronto, onEstado, onInteracao, onErro; comandos trocarPagina e aplicarFiltros. | página hospedeira local |
| 5 | 5:30–6:45 | Allowlist de origem (global por ambiente hoje) e o que acontece quando a origem não está nela. | /dashboards |
| 6 | 6:45–8:00 | Rotacionar 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ça | Onde roda | O que faz |
|---|---|---|
Chave de embed (keyId + secret) | ingestia.io (UI) | nasce ao ligar o embed; o segredo aparece uma vez só |
| Token | o seu servidor | prova assinada de que este usuário vê este painel por N minutos |
embed-sdk.js | navegador do seu usuário | cria 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)
// 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
<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ície | Estado | Quando usar |
|---|---|---|
Protocolo do SDK (postMessage v1, fail-closed) | GA-candidato | os 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) | Preview | painel dentro de portal próprio — é o uso certo hoje |
| Allowlist de origem por workspace | Roadmap | não existe: hoje a allowlist é global por ambiente |
| Eventos de cross-filter (clique em barra/fatia) | Roadmap | nã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
| Sintoma | Causa provável | O que fazer |
|---|---|---|
Tela de recusa ou onErro logo ao abrir | token expirado, ou origem fora da allowlist | emita token novo; peça a inclusão do domínio |
| Segredo no código do navegador | assinatura foi feita no cliente | assine no servidor; rotacione o segredo imediatamente |
| Filtros não aplicam | aplicarFiltros chamado antes do onPronto | aplique dentro ou depois de onPronto |
filtros_invalidos | estrutura de filtro inválida | use os ids do editor; leia o onEstado para descobri-los |
| Todo token parou de valer de uma vez | o segredo foi rotacionado | atualize a variável de ambiente no servidor |
| O painel sumiu do portal | o painel de origem foi despublicado | republique — token nunca abre painel despublicado |
| Prometeram ao cliente latência de carregamento | renderização e latência em produção não são medidas | nã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):
- Num painel publicado (por exemplo "Aurora — Vendas"), abra Compartilhar e clique Ligar o embed. Escolha validade de 15 min.
- 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. - Use Gerar link de teste e abra o link numa aba. Confirme que o painel carrega antes de escrever qualquer código.
- Monte localmente uma página HTML simples com um
<iframe>apontando para esse link de teste. - Inclua o
embed-sdk.jse assineonProntoeonErro. Confirme queonProntodispara com{ dashboardId, pagina }. - Chame
aplicarFiltrosantes doonProntoe observe o filtro não pegar; mova a chamada para dentro doonProntoe confirme que agora aplica (e que o iframe recarrega). - 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_autorizadanoonErro. - 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".