Documentação Provei

Como Instalar

No painel, a página Instalação já mostra o código pronto para a sua conta. Aqui, cole o UUID recebido do Provei para personalizar os exemplos. Enquanto o valor não for um UUID aceito pela rota, os exemplos continuam usando o placeholder seguro SEU_USER_ID.

Linha do widget

Uma instalação Home para todo o catálogo de Móveis

Use o endpoint único /api/v2/snippet/[UUID]. Com auto_detect_url ativo, o servidor encontra a página do produto e resolve o modo canônico furniture, revestimento ou pisos. O runtime Home usa o iframe /widget-v2/[UUID]; não instale snippets separados para cada família.

Antes de começar

  1. Peça ao suporte/admin do Provei para registrar a origem principal e as origens adicionais da loja. Esse cadastro não é um painel de autoatendimento do cliente.
  2. Confirme que o UUID da conta está liberado e provisionado para o modo Home, Fashion ou Medidas que você pretende usar.
  3. Cadastre o produto e confirme que ele está ativo e corresponde à origem e ao caminho normalizados da página atual.

Os pré-requisitos de plano ativo e créditos se aplicam aos runtimes Home e Fashion. Eles não são uma condição genérica do runtime de Medidas; essa linha depende da resposta de elegibilidade e das ações habilitadas.

Comportamento por linha

Home — Móveis, Revestimentos e Pisos

Quando auto_detect_url está habilitado, o servidor resolve furniture, revestimento ou pisos a partir do produto autorizado e da URL da página. Não envie um identificador ou tipo de produto pelo navegador: a decisão pertence ao servidor.

Fashion — Provador Virtual

Fashion usa /api/v2/snippet-fashion/[UUID], o matcher de URL é o comportamento padrão e o produto é encontrado pela página autorizada. Não há override manual de produto documentado para esse runtime, e Fashion não usa a Promise ou os eventos de prontidão do Home.

Medidas — resposta de elegibilidade

Medidas consulta /api/v2/widget-measurements/match e usa sua própria resposta JSON de capacidades e seu próprio runtime. O objeto público, quando a ação é elegível, é window.ReverbsWidgetV2Measurements; ele não é o objeto Home e não fornece a Promise de prontidão do Home.

Escolha o tipo de instalação

Escolha o posicionamento que combina com a página. O código abaixo usa sempre a origem pública canônica do Provei; não substitua essa origem por uma origem de preview ou pela origem da página de documentação.

Use o loader de referência fornecido pelo Provei quando quiser uma instalação Home com controle de prontidão, erro e duplicidade. O bloco não informa um produto no navegador.

Loader de referência Home (fornecido pelo Provei)
<script
  data-provei-widget-loader
  data-provei-user-id="SEU_USER_ID"
>
(function () {
  "use strict";

  var loader = document.currentScript;
  var ownerIdValue = loader && loader.getAttribute("data-provei-user-id");
  var ownerUuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

  function canonicalOwnerId(value) {
    return typeof value === "string" && ownerUuidPattern.test(value)
      ? value.toLowerCase()
      : null;
  }

  var ownerId = canonicalOwnerId(ownerIdValue);
  function dispatchError(detail) {
    if (typeof CustomEvent !== "undefined") {
      window.dispatchEvent(
        new CustomEvent("reverbs:error", { detail: detail })
      );
    }
  }

  if (!ownerId) {
    dispatchError({
      code: "invalid_owner_id",
      message: "O User ID do widget Provei deve ser um UUID válido.",
      userId: ownerIdValue || ""
    });
    return;
  }

  var runtimeUrl =
    "https://app.provei.ai" + "/api/v2/snippet/" + encodeURIComponent(ownerId);
  var runtime = null;
  var settled = false;

  function cleanup() {
    window.removeEventListener("reverbs:ready", reportReady);
    window.removeEventListener("reverbs:error", reportError);
    if (runtime) {
      runtime.removeEventListener("error", reportScriptError);
    }
  }

  function normalizeError(input, fallbackCode, fallbackMessage) {
    var detail = input && input.detail ? input.detail : input;
    return {
      code: detail && typeof detail.code === "string"
        ? detail.code
        : fallbackCode,
      message: detail && typeof detail.message === "string"
        ? detail.message
        : fallbackMessage,
      userId: detail && typeof detail.userId === "string"
        ? detail.userId
        : null
    };
  }

  function reportError(input) {
    var detail = normalizeError(
      input,
      "initialization_failed",
      "Não foi possível inicializar o widget Provei."
    );
    if (canonicalOwnerId(detail.userId) !== ownerId || settled) {
      return;
    }
    settled = true;
    cleanup();
    console.error("[Provei] " + detail.code + ": " + detail.message);
  }

  function reportReady(input) {
    var detail = input && input.detail ? input.detail : null;
    if (!detail || canonicalOwnerId(detail.userId) !== ownerId) {
      return;
    }
    if (canonicalOwnerId(window.reverbsWidgetOwnerId) !== ownerId) {
      return;
    }
    var widget = detail.widget || window.reverbsWidget;
    if (!widget || settled) {
      return;
    }
    settled = true;
    cleanup();
    if (
      window.__REVERBS_DEBUG__ === true ||
      /[?&]reverbs_debug=(1|true)(&|$)/.test(window.location.search || "")
    ) {
      console.info("[Provei] Widget pronto.");
    }
  }

  function reportScriptError() {
    if (runtime && runtime.parentNode) {
      runtime.parentNode.removeChild(runtime);
    }
    dispatchError({
      code: "script_load_failed",
      message: "Não foi possível carregar o runtime do widget Provei.",
      userId: ownerId
    });
    runtime = null;
  }

  function reportOwnerConflict() {
    dispatchError({
      code: "owner_conflict",
      message: "Já existe um widget Provei de outro proprietário nesta página.",
      userId: ownerId
    });
  }

  // Registre os listeners antes de anexar o runtime.
  window.addEventListener("reverbs:ready", reportReady);
  window.addEventListener("reverbs:error", reportError);

  var publicOwnerValue =
    typeof window.reverbsWidgetOwnerId === "string"
      ? window.reverbsWidgetOwnerId
      : "";
  var publicOwnerId = canonicalOwnerId(publicOwnerValue);
  if (publicOwnerValue && publicOwnerId !== ownerId) {
    reportOwnerConflict();
    return;
  }

  function hasDifferentOwner(selector) {
    var scripts = document.querySelectorAll(selector);
    for (var index = 0; index < scripts.length; index += 1) {
      var candidateOwner = canonicalOwnerId(
        scripts[index].getAttribute("data-provei-user-id")
      );
      if (candidateOwner !== ownerId) {
        return true;
      }
    }
    return false;
  }

  var markedScripts =
    'script[data-provei-widget-loader][data-provei-user-id], ' +
    'script[data-provei-runtime][data-provei-user-id]';
  if (hasDifferentOwner(markedScripts)) {
    reportOwnerConflict();
    return;
  }

  // Uma instalação do mesmo User ID reutiliza a promessa singleton existente.
  if (
    window.reverbsWidgetReady &&
    typeof window.reverbsWidgetReady.then === "function"
  ) {
    if (publicOwnerId !== ownerId) {
      reportOwnerConflict();
      return;
    }
    window.reverbsWidgetReady.then(
      function (widget) {
        reportReady({ detail: { userId: ownerId, widget: widget } });
      },
      reportError
    );
    return;
  }

  var markedRuntimes = document.querySelectorAll(
    "script[data-provei-runtime][data-provei-user-id]"
  );
  for (var runtimeIndex = 0; runtimeIndex < markedRuntimes.length; runtimeIndex += 1) {
    if (
      canonicalOwnerId(
        markedRuntimes[runtimeIndex].getAttribute("data-provei-user-id")
      ) === ownerId
    ) {
      return;
    }
  }

  runtime = document.createElement("script");
  runtime.src = runtimeUrl;
  runtime.async = true;
  runtime.crossOrigin = "anonymous";
  runtime.setAttribute("data-provei-runtime", "");
  runtime.setAttribute("data-provei-user-id", ownerId);
  runtime.addEventListener("error", reportScriptError);
  document.head.appendChild(runtime);
})();
</script>

Este é o loader de referência fornecido pelo Provei. O contrato para um loader externo hospedado pela loja está em Loader personalizado (Home).

Alternativa Home — tag direta
<!-- Provei Widget - Móveis -->
<script src="https://app.provei.ai/api/v2/snippet/SEU_USER_ID" async crossorigin="anonymous"></script>

O que acontece automaticamente

  • A origem e o proprietário são validados antes de o runtime ser autorizado.
  • Home encontra o produto pela URL e escolhe furniture, revestimento ou pisos no servidor quando a detecção está ativa.
  • Fashion usa seu matcher de URL; não há fallback de override manual.
  • Medidas consulta a elegibilidade e exibe os links de medidas configurados quando houver uma ação habilitada.

Loader externo hospedado pelo cliente (Home)

O bloco anterior é o loader de referência fornecido pelo Provei. A loja também pode autorar e hospedar o próprio script clássico em um CDN ou domínio sob seu controle. Nesse caso, a página hospeda apenas a tag externa abaixo; os dois marcadores identificam o proprietário, mas não substituem a autorização de origem.

Tag na página da loja
<script
  src="https://cdn.sua-loja.example/provei-home-loader.js"
  data-provei-widget-loader
  data-provei-user-id="SEU_USER_ID"
></script>

O src é deliberadamente externo e deve apontar para o script clássico hospedado pela própria loja. Não transforme esse script em módulo, não faça fetch seguido de eval e não copie o loader de referência para a página do produto.

Obrigações do script externo

  • Capture de forma síncrona o elemento que iniciou o script com document.currentScript. Leia dele o data-provei-user-id e valide o mesmo UUID aceito por /api/v2/snippet/[userId] antes de construir seletores ou fazer qualquer requisição.
  • Registre, antes de inserir o runtime, listeners filtrados pelo mesmo proprietário para reverbs:ready e reverbs:error. Ignore eventos terminais cujo userId seja de outra instalação.
  • Insira exatamente o runtime clássico em https://app.provei.ai/api/v2/snippet/<encoded UUID>, usando encodeURIComponent no UUID. O elemento precisa ter async, crossOrigin="anonymous", o atributo data-provei-runtime e o mesmo data-provei-user-id.
  • Consuma window.reverbsWidgetReady somente quando window.reverbsWidgetOwnerId corresponder ao UUID capturado. Uma Promise de outro proprietário é um conflito e não deve ser controlada pelo loader da loja.
  • Para Home, nunca envie no navegador um ID de produto ou um tipo de produto. A origem e o caminho atuais, junto com a configuração provisionada, definem a correspondência no servidor.

Não confunda os papéis: o Provei hospeda o runtime gerado em app.provei.ai; a loja hospeda o loader externo que decide quando inseri-lo. O contrato é compatível com um script clássico independente, não com uma cópia local do runtime.

Pronto, erro e ciclo de vida

Fixe o UUID esperado no código de consumo e filtre cada evento terminal antes de agir. O exemplo abaixo mostra as duas ordens seguras: registre listeners antes de recarregar/inserir o loader, ou aguarde a Promise existente somente depois de confirmar o mesmo proprietário.

Consumir a instância Home com segurança
(function () {
  var expectedOwnerId = "11111111-1111-4111-8111-111111111111";
  var finished = false;

  function detailOf(event) {
    return event && event.detail ? event.detail : event;
  }

  function belongsToThisOwner(detail) {
    return detail && detail.userId === expectedOwnerId;
  }

  function cleanup() {
    window.removeEventListener("reverbs:ready", onReady);
    window.removeEventListener("reverbs:error", onError);
  }

  function onReady(event) {
    var detail = detailOf(event);
    if (finished || !belongsToThisOwner(detail)) return;
    var widget = detail.widget;
    if (!widget) return;
    finished = true;
    cleanup();
    // O runtime está inicializado; abrir depende de uma ação explícita.
  }

  function onError(event) {
    var detail = detailOf(event);
    if (finished || !belongsToThisOwner(detail)) return;
    finished = true;
    cleanup();
    console.error(detail.code, detail.message);
  }

  // Opção 1: execute antes de inserir ou recarregar a tag do loader.
  window.addEventListener("reverbs:ready", onReady);
  window.addEventListener("reverbs:error", onError);

  // Opção 2: se o loader já existe, reutilize a Promise do mesmo owner.
  if (window.reverbsWidgetOwnerId === expectedOwnerId &&
      window.reverbsWidgetReady &&
      typeof window.reverbsWidgetReady.then === "function") {
    window.reverbsWidgetReady.then(
      function (widget) {
        onReady({ detail: { userId: expectedOwnerId, widget: widget } });
      },
      onError
    );
  }
})();

reverbs:ready confirma apenas o estado de runtime inicializado. Ele não prova que o plano, créditos ou correspondência do produto produziram um launcher visível. O erro terminal usa code, message e userId; inclua também invalid_owner_id e owner_conflict no tratamento.

Depois de ready, remova somente os listeners da loja e mantenha o runtime vivo: o handler de prontidão não deve chamar open() nem remover o elemento de runtime. A remoção do runtime pertence às fronteiras de falha de carregamento (script_load_failed) ou de destroy(), não ao caminho de sucesso. Essa limpeza de listeners é garantida após um resultado terminal; não há timeout oculto para uma requisição de rede que permaneça indefinidamente pendente.

Os métodos Home documentados são open, close e destroy. Eventos de abertura, fechamento e destruição são observáveis em reverbs:open, reverbs:close e reverbs:destroy.

Aplicações SPA

Em uma SPA, mantenha um único loader clássico global. O runtime acompanha mudanças de rota e rematcha a URL autorizada; não reinsira a tag a cada pushState, replaceState, popstate ou hashchange.

Duplicidade e conflito de proprietário

  • Cargas repetidas ou concorrentes do mesmo UUID compartilham a Promise singleton e o objeto do mesmo runtime.
  • Um segundo UUID não substitui o primeiro: emita owner_conflict em reverbs:error e não acrescente um runtime do segundo proprietário.
  • Após destroy(), o runtime pertencente e seus listeners internos são removidos; reinserir o mesmo loader pode iniciar uma nova instalação limpa. Listeners registrados pela loja precisam seguir a própria regra de limpeza terminal.

Onde colar o código

Em qualquer plataforma, coloque um único loader clássico em uma área global do site, de preferência no rodapé e antes de </body>. Os nomes e caminhos dos menus administrativos são dependentes da versão do fornecedor; use o editor equivalente de código global da sua instalação.

Shopify — editor global (menus variam por versão)

  1. Abra o editor de código ou de tema disponibilizado pelo admin atual.
  2. Localize o layout global que envolve as páginas públicas da loja.
  3. Cole o loader de referência Home uma única vez no rodapé, imediatamente antes de </body>.
  4. Salve e publique a versão editada.
Loader de referência Home (fornecido pelo Provei)
<script
  data-provei-widget-loader
  data-provei-user-id="SEU_USER_ID"
>
(function () {
  "use strict";

  var loader = document.currentScript;
  var ownerIdValue = loader && loader.getAttribute("data-provei-user-id");
  var ownerUuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

  function canonicalOwnerId(value) {
    return typeof value === "string" && ownerUuidPattern.test(value)
      ? value.toLowerCase()
      : null;
  }

  var ownerId = canonicalOwnerId(ownerIdValue);
  function dispatchError(detail) {
    if (typeof CustomEvent !== "undefined") {
      window.dispatchEvent(
        new CustomEvent("reverbs:error", { detail: detail })
      );
    }
  }

  if (!ownerId) {
    dispatchError({
      code: "invalid_owner_id",
      message: "O User ID do widget Provei deve ser um UUID válido.",
      userId: ownerIdValue || ""
    });
    return;
  }

  var runtimeUrl =
    "https://app.provei.ai" + "/api/v2/snippet/" + encodeURIComponent(ownerId);
  var runtime = null;
  var settled = false;

  function cleanup() {
    window.removeEventListener("reverbs:ready", reportReady);
    window.removeEventListener("reverbs:error", reportError);
    if (runtime) {
      runtime.removeEventListener("error", reportScriptError);
    }
  }

  function normalizeError(input, fallbackCode, fallbackMessage) {
    var detail = input && input.detail ? input.detail : input;
    return {
      code: detail && typeof detail.code === "string"
        ? detail.code
        : fallbackCode,
      message: detail && typeof detail.message === "string"
        ? detail.message
        : fallbackMessage,
      userId: detail && typeof detail.userId === "string"
        ? detail.userId
        : null
    };
  }

  function reportError(input) {
    var detail = normalizeError(
      input,
      "initialization_failed",
      "Não foi possível inicializar o widget Provei."
    );
    if (canonicalOwnerId(detail.userId) !== ownerId || settled) {
      return;
    }
    settled = true;
    cleanup();
    console.error("[Provei] " + detail.code + ": " + detail.message);
  }

  function reportReady(input) {
    var detail = input && input.detail ? input.detail : null;
    if (!detail || canonicalOwnerId(detail.userId) !== ownerId) {
      return;
    }
    if (canonicalOwnerId(window.reverbsWidgetOwnerId) !== ownerId) {
      return;
    }
    var widget = detail.widget || window.reverbsWidget;
    if (!widget || settled) {
      return;
    }
    settled = true;
    cleanup();
    if (
      window.__REVERBS_DEBUG__ === true ||
      /[?&]reverbs_debug=(1|true)(&|$)/.test(window.location.search || "")
    ) {
      console.info("[Provei] Widget pronto.");
    }
  }

  function reportScriptError() {
    if (runtime && runtime.parentNode) {
      runtime.parentNode.removeChild(runtime);
    }
    dispatchError({
      code: "script_load_failed",
      message: "Não foi possível carregar o runtime do widget Provei.",
      userId: ownerId
    });
    runtime = null;
  }

  function reportOwnerConflict() {
    dispatchError({
      code: "owner_conflict",
      message: "Já existe um widget Provei de outro proprietário nesta página.",
      userId: ownerId
    });
  }

  // Registre os listeners antes de anexar o runtime.
  window.addEventListener("reverbs:ready", reportReady);
  window.addEventListener("reverbs:error", reportError);

  var publicOwnerValue =
    typeof window.reverbsWidgetOwnerId === "string"
      ? window.reverbsWidgetOwnerId
      : "";
  var publicOwnerId = canonicalOwnerId(publicOwnerValue);
  if (publicOwnerValue && publicOwnerId !== ownerId) {
    reportOwnerConflict();
    return;
  }

  function hasDifferentOwner(selector) {
    var scripts = document.querySelectorAll(selector);
    for (var index = 0; index < scripts.length; index += 1) {
      var candidateOwner = canonicalOwnerId(
        scripts[index].getAttribute("data-provei-user-id")
      );
      if (candidateOwner !== ownerId) {
        return true;
      }
    }
    return false;
  }

  var markedScripts =
    'script[data-provei-widget-loader][data-provei-user-id], ' +
    'script[data-provei-runtime][data-provei-user-id]';
  if (hasDifferentOwner(markedScripts)) {
    reportOwnerConflict();
    return;
  }

  // Uma instalação do mesmo User ID reutiliza a promessa singleton existente.
  if (
    window.reverbsWidgetReady &&
    typeof window.reverbsWidgetReady.then === "function"
  ) {
    if (publicOwnerId !== ownerId) {
      reportOwnerConflict();
      return;
    }
    window.reverbsWidgetReady.then(
      function (widget) {
        reportReady({ detail: { userId: ownerId, widget: widget } });
      },
      reportError
    );
    return;
  }

  var markedRuntimes = document.querySelectorAll(
    "script[data-provei-runtime][data-provei-user-id]"
  );
  for (var runtimeIndex = 0; runtimeIndex < markedRuntimes.length; runtimeIndex += 1) {
    if (
      canonicalOwnerId(
        markedRuntimes[runtimeIndex].getAttribute("data-provei-user-id")
      ) === ownerId
    ) {
      return;
    }
  }

  runtime = document.createElement("script");
  runtime.src = runtimeUrl;
  runtime.async = true;
  runtime.crossOrigin = "anonymous";
  runtime.setAttribute("data-provei-runtime", "");
  runtime.setAttribute("data-provei-user-id", ownerId);
  runtime.addEventListener("error", reportScriptError);
  document.head.appendChild(runtime);
})();
</script>

Este é o loader de referência fornecido pelo Provei. O contrato para um loader externo hospedado pela loja está em Loader personalizado (Home).

Origens autorizadas e CSP

Registre a origem da loja

O suporte/admin do Provei, e não um painel de autoatendimento do cliente, deve registrar a origem principal da loja e cada origem adicional que realmente carregará o widget. Envie a origem HTTPS completa, sem caminho, query ou fragmento.

  • https://loja.exemplo.com.br e https://www.loja.exemplo.com.br são origens diferentes; registre cada uma quando ambas forem usadas.
  • Registre previews ou checkout somente se o runtime for carregado neles. Não use um curinga para substituir a autorização explícita.
  • O User ID e os marcadores HTML identificam o proprietário, mas não autorizam uma origem não registrada.

Correspondência da página do produto

A correspondência compara a origem e o caminho do produto ativo com a página atual depois da normalização. Protocolo e host são tratados na forma canônica da URL, e a comparação não diferencia maiúsculas de minúsculas; query string e hash não participam da identidade; barras finais são removidas para evitar uma diferença artificial. O caminho continua sendo relevante para escolher o produto.

Se houver uma correspondência ambígua, indisponível ou não provisionada, o resultado é no-match: o servidor não escolhe um produto arbitrário e o loader não é rejeitado por isso. Se show_on_all_pages e button_enabled estiverem ativos, um botão ainda pode aparecer sem correspondência;open() continua exigindo uma correspondência elegível. Sem essa configuração, o script pode ter sido autorizado e inicializado, mas nenhum launcher aparecerá.

Content Security Policy

Comece com uma política mínima para o runtime. O domínio do Provei deve aparecer em script-src, connect-src e frame-src. Não inclua hosts de analytics ou de gerenciadores de tags na política base se esses recursos não estiverem habilitados.

CSP base mínima
Content-Security-Policy:
  script-src 'self' https://app.provei.ai;
  connect-src 'self' https://app.provei.ai;
  frame-src 'self' https://app.provei.ai;
  style-src 'self';
  img-src 'self';

O loader inline de referência e os estilos inline atuais precisam seguir a política de inline escolhida pelo site: use um nonce/hash aplicado corretamente ou, quando essa for a decisão de segurança da loja, 'unsafe-inline' emscript-src e style-src. Um nonce/hash do script não autoriza estilos diferentes, e vice-versa.

Adições opcionais — inline e imagens
script-src 'self' https://app.provei.ai 'nonce-<NONCE-DO-SITE>';
style-src 'self' 'nonce-<NONCE-DO-SITE>';
img-src 'self' https://cdn.configurado-pela-loja.example data: blob:;

Substitua o CDN de exemplo pelos CDNs configurados para as superfícies de imagem habilitadas. Mantenha apenas as fontes de imagem necessárias, incluindodata: e blob: somente se a experiência realmente as usar.

Bloco opcional — somente com GTM/Analytics habilitado
script-src 'self' https://app.provei.ai https://www.googletagmanager.com;
connect-src 'self' https://app.provei.ai https://www.google-analytics.com https://region1.google-analytics.com;
frame-src 'self' https://app.provei.ai https://www.googletagmanager.com;

O bloco GTM/Analytics é separado de propósito: acrescente os hosts acima somente quando o GTM estiver configurado e quando as tags publicadas realmente fizerem essas chamadas. As tags do container podem exigir outros hosts; permita-os individualmente, conforme a configuração aprovada.

Verificação ordenada

  1. Abra uma página de produto autorizada e já provisionada; inspecione as requisições do navegador a partir dessa página.
  2. Opção 1: instale os listeners antes de recarregar ou inserir o loader. Opção 2: quando ele já estiver carregado, aguarde a Promise existente e confirme o mesmo owner.
  3. Confira o proprietário, a URL canônica e o CORS. Para referência ou loader personalizado, confira também a quantidade de elementos marcada; para a tag direta, use a checagem específica abaixo antes de testar o botão.

Home — referência/loader personalizado

Os marcadores abaixo são verificáveis somente no loader de referência fornecido pelo Provei ou no loader externo personalizado da loja. A tag direta não cria data-provei-widget-loader; use a checagem separada logo abaixo para esse formato. Rode a opção 1 antes de reinserir a tag ou a opção 2 se a Promise do mesmo owner já existir.

Duas ordens seguras para verificar Home
var expectedOwnerId = "11111111-1111-4111-8111-111111111111";
var expectedRuntimeSrc =
  "https://app.provei.ai/api/v2/snippet/" + encodeURIComponent(expectedOwnerId);

function assertHomeInstallation() {
  var loaders = document.querySelectorAll(
    'script[data-provei-widget-loader][data-provei-user-id]'
  );
  var runtimes = document.querySelectorAll(
    'script[data-provei-runtime][data-provei-user-id]'
  );
  console.assert(loaders.length === 1, "um loader marcado");
  console.assert(runtimes.length === 1, "um runtime marcado");

  var loader = loaders[0];
  var runtime = runtimes[0];
  console.assert(loader.getAttribute("data-provei-user-id") === expectedOwnerId);
  console.assert(runtime.getAttribute("data-provei-user-id") === expectedOwnerId);
  console.assert(runtime.src === expectedRuntimeSrc);
  console.assert(runtime.crossOrigin === "anonymous");
  console.assert(window.reverbsWidgetOwnerId === expectedOwnerId);

  var ready = window.reverbsWidgetReady;
  console.assert(ready && typeof ready.then === "function");
  if (ready && typeof ready.then === "function") {
    ready.then(function (widget) {
      console.assert(widget === window.reverbsWidget);
      console.assert(typeof widget.open === "function");
      console.assert(typeof widget.close === "function");
      console.assert(typeof widget.destroy === "function");
    });
  }
}

// Opção 1: listeners antes da inserção/reload.
window.addEventListener("reverbs:ready", function (event) {
  if (event.detail && event.detail.userId === expectedOwnerId) {
    console.assert(event.detail.widget === window.reverbsWidget);
  }
});
// Opção 2: use a Promise somente depois de confirmar o owner.
assertHomeInstallation();

Depois da inicialização, se o botão estiver visível, clique nele e confirme o iframe /widget-v2/[UUID]. Com show_on_all_pages, o botão pode aparecer sem produto elegível, mas open() será um no-op até existir uma correspondência. Uma inicialização correta sem produto elegível é diferente de um erro de runtime.

Home — verificação da tag direta

Na instalação direta, confira o único elemento de script que aponta para o endpoint Home. Ele não é um loader marcado nem um runtime inserido por loader: a própria tag baixa o runtime gerado.

Checar a tag direta Home
var expectedOwnerId = "11111111-1111-4111-8111-111111111111";
var expectedRuntimeSrc =
  "https://app.provei.ai/api/v2/snippet/" + encodeURIComponent(expectedOwnerId);

var directTags = Array.prototype.filter.call(document.scripts, function (script) {
  return script.src === expectedRuntimeSrc &&
    !script.hasAttribute("data-provei-widget-loader") &&
    !script.hasAttribute("data-provei-runtime");
});
console.assert(directTags.length === 1, "uma tag direta Home");
if (directTags.length === 1) {
  console.assert(directTags[0].async, "tag direta assíncrona");
  console.assert(directTags[0].crossOrigin === "anonymous", "CORS anônimo");
}
console.assert(window.reverbsWidgetOwnerId === expectedOwnerId);
console.assert(
  window.reverbsWidgetReady &&
  typeof window.reverbsWidgetReady.then === "function",
  "Promise do runtime Home"
);

Fashion — verificação separada

Em Fashion, verifique a tag do endpoint de moda, o matcher da URL e o objeto window.reverbsWidgetFashion no momento da ação do comprador. Fashion não expõe a Promise ou o evento de prontidão do Home; o teste deve disparar a ação de experimentar e observar a interface e os eventos próprios dessa linha.

Medidas — verificação separada

Em Medidas, confirme a requisição GET a /api/v2/widget-measurements/match, a resposta de capacidades e a renderização das ações elegíveis. Verifique o global exato window.ReverbsWidgetV2Measurements e não procure window.reverbsWidgetReady: esse é um runtime independente.

O que o verificador estático pode provar

O verificador da página Instalação do painel (etapa Testar) examina somente os marcadores no HTML bruto publicado. Ele pode localizardata-provei-widget-loader junto com um data-provei-user-id e classificar a instalação como direta ou custom_loader; essa evidência mantém runtimeVerified: false.

Esse teste não executa JavaScript e não prova CORS, autorização, correspondência de produto, carregamento do iframe, Promise ou launcher. Ele também não encontra injeção feita depois por JavaScript do cliente ou por um gerenciador de consentimento. Para provar runtime, use a verificação ordenada em um navegador na origem autorizada.

Exemplo de evidência estática
{
  "found": true,
  "classification": "custom_loader",
  "runtimeVerified": false
}

Solução de problemas

O botão ou ação não aparece

  1. Confirme a origem registrada, o UUID válido, o perfil provisionado e o produto ativo na URL normalizada.
  2. Para Home, confira se o modo resolvido é furniture, revestimento ou pisos; para Medidas, confira se há ação elegível habilitada.
  3. Em Home e Fashion, confirme também plano ativo e créditos disponíveis. Não use esse diagnóstico como pré-requisito de Medidas.
  4. Em Inline ou Botão, confirme o container ou data-reverbs-trigger da página.

Leia o status HTTP na página autorizada

Não abra a URL do script na barra de endereço: inspecione as requisições do snippet a partir de uma página de produto autorizada.

  • 400: UUID inválido ou parâmetros inválidos.
  • 401: a requisição não apresentou a autenticação/recibo exigida pelo fluxo.
  • 403: origem ausente, não autorizada ou proprietário não permitido.
  • 404: perfil indisponível, não provisionado ou produto não encontrado para o contexto.
  • 410 em Medidas: não há ação elegível habilitada para a página.
  • 500: falha de geração do snippet/runtime; preserve o código da resposta para o suporte.

Erro de script, CSP ou duplicidade

  • invalid_owner_id indica que o UUID não passou pela mesma validação da rota; corrija o valor antes de reinserir o loader.
  • owner_conflict indica outro proprietário na página; mantenha um único loader e um único runtime.
  • script_load_failed pede revisão de CSP, rede e do atributo crossOrigin="anonymous".
  • Verifique a política base, os nonces ou hashes inline, as fontes de imagem e o bloco GTM opcional sem abrir o endpoint diretamente.

Precisa de ajuda?

Envie ao suporte a origem registrada, o UUID da conta sem expor credenciais, a página de produto e o status HTTP observado. Para tabelas e ações de Medidas, consulte também o guia específico.