Widget — Integração técnica
Guia para programadores: script, payload de init(), associação e criação de empresa, ciclo de vida, restauro autenticado (restore_id), CSP e verificação.
Páginas de percurso: Visão geral · B2C · B2B.
Edição de referência: 26 de outubro de 2026. O restauro entre dispositivos acrescenta-se ao payload de empresa. Antes de o ativar, confirma que provisionRestoreId está disponível na tua instalação.
A interface corre num iframe. O site anfitrião não precisa de Angular, de um cliente Supabase nem de acesso à base da Quoting24.
Escolher o tipo de integração
| Caso | Implementação | Continuidade |
|---|---|---|
| Site público, visitante desconhecido | Só script. Formulário de contacto | Sessão de convidado no mesmo browser |
| Site com dados do cliente | Script e init() com perfil e empresa |
Sessão de convidado pré-preenchida e associação de empresa |
| Portal com histórico pessoal entre dispositivos | Também um backend para external_user_id e restore_id |
Histórico pessoal no canal widget autorizado |
Associar uma empresa e restaurar o histórico são operações distintas. external_id, company_name, o nome, o e-mail e o telefone não autorizam as conversas de outra pessoa. Um utilizador autenticado no site continua a ser um convidado do widget até forneceres uma credencial de restauro válida.
1. Configuração a partir da Quoting24
Em Definições → Canais, abre o canal widget e recupera:
- URL completo do script e chave pública
- domínios autorizados, incluindo testes e desenvolvimento local
- logótipo, cor, posição, saudação, boas-vindas e horários
Usa o URL e a chave do mesmo ambiente. Nos exemplos substitui HOST_WIDGET e CHAVE_PUBLICA_WIDGET. A chave no URL é pública e seleciona o canal: não é uma credencial pessoal nem um segredo de integração de backend.
Os domínios são nomes de host (shop.example.com, localhost), sem https://, porta, caminho ou prefixo *.. example.com autoriza também os subdomínios. Uma lista vazia não aplica restrições. Para o teste local serve a página por HTTP, não como ficheiro.
| Valor | Onde o conservar | Finalidade |
|---|---|---|
| Chave pública | URL do script | Seleciona o canal widget |
| Segredo privado da integração | Só segredos do backend | Autoriza criar e reutilizar restore_id |
restore_id |
Arquivo privado por utilizador; na página só com sessão autenticada | Indica que histórico restaurar |
| Credencial de sessão de convidado | localStorage do site, por chave de widget, gerida pelo loader |
Continuidade de convidado no mesmo browser |
O restauro exige integração de backend, endpoint privado e segredo, separados do código público. Vê a secção 6.
2. Instalação
Botão flutuante
Uma só vez, antes de </body>:
O botão aparece depois de carregar as definições gráficas. Um visitante novo abre o formulário com telefone e nome facultativo. O loader guarda a credencial de convidado no localStorage, separada por chave. Se o armazenamento estiver bloqueado, a continuidade pode limitar-se à página atual.
O loader expõe um único window.Quoting24Widget. Um segundo script não cria um segundo widget.
Chat incorporado
Contentor antes do script, altura explícita:
<section aria-labelledby="titulo-assistencia">
<h2 id="titulo-assistencia">Assistência</h2>
<div id="chat-assistencia" style="width: 100%; height: 600px;"></div>
</section>
<script
src="https://HOST_WIDGET/assets/widget/script.js?key=CHAVE_PUBLICA_WIDGET&mode=embedded&target=chat-assistencia"
async
></script>
target é o ID, sem #. O iframe preenche o contentor. Não há botão flutuante. open(), close() e toggle() não têm efeito.
Esperar pelo loader
Para dados pré-preenchidos usa este módulo em vez da etiqueta estática, importado uma só vez no layout principal. Os exemplos são módulos JavaScript: bundler, ou <script type="module" src="/assets/assistencia.js"></script>.
// /assets/quoting24-loader.js
const widgetScriptUrl =
"https://HOST_WIDGET/assets/widget/script.js?key=CHAVE_PUBLICA_WIDGET";
let widgetPromise;
export function loadWidget() {
if (window.Quoting24Widget) {
return Promise.resolve(window.Quoting24Widget);
}
if (widgetPromise) return widgetPromise;
widgetPromise = new Promise((resolve, reject) => {
const script = document.createElement("script");
script.id = "quoting24-widget-script";
script.src = widgetScriptUrl;
script.async = true;
script.onload = () => {
if (window.Quoting24Widget) resolve(window.Quoting24Widget);
else {
script.remove();
reject(new Error("A API do widget não está disponível"));
}
};
script.onerror = () => {
script.remove();
reject(new Error("Não foi possível carregar o widget"));
};
document.body.appendChild(script);
}).catch((error) => {
widgetPromise = undefined;
throw error;
});
return widgetPromise;
}
import { loadWidget } from "/assets/quoting24-loader.js";
const widget = await loadWidget();
widget.init({
external_id: "52",
company_name: "Ricambi Stella S.r.l.",
name: "mario.rossi",
phone: "+393331234567",
email: "amministrazione@ricambi-stella.example",
});
Para o modo incorporado acrescenta &mode=embedded&target=chat-assistencia ao URL e monta primeiro o contentor. Não dupliques este carregamento com uma etiqueta script estática.
loadWidget() indica que a API está disponível. init() devolve void: põe o perfil em fila se o iframe não estiver pronto e não espera pela sessão. Para o resultado usa isReady() e hasSessionError().
3. Campos de init()
Todos os valores são cadeias. Omite os desconhecidos. Não envies null, IDs numéricos nem um e-mail vazio. Se um campo ultrapassar os limites ou o e-mail estiver mal formado, o pedido é rejeitado.
| Campo | Limite e comportamento |
|---|---|
external_id |
Máximo de 100 caracteres. Referência de empresa comparada exatamente com web_code, na organização do widget. Os espaços não são retirados |
company_name |
Máximo de 250 caracteres depois de remover os espaços iniciais e finais. Cria uma empresa em falta só juntamente com external_id. Uma cadeia vazia ou só de espaços não ativa a criação |
name |
Máximo de 250 caracteres. Nome do contacto; pode atualizá-lo na sessão atual |
email |
Endereço válido. Na inicialização pública só é guardado numa empresa nova. Não é escrito no contacto nem numa empresa já existente |
phone |
Máximo de 80 caracteres. Número do contacto; prefere o formato internacional, por exemplo +393331234567. Um conflito com um número existente não junta contactos nem chats |
external_user_id |
Facultativo no caso base. ID estável da pessoa, não vazio, máximo de 100 caracteres após o trim. Por si só não restaura o histórico |
restore_id |
Credencial privada, máximo de 200 caracteres, obtida do backend. Exige o external_user_id correspondente. Usa o valor devolvido sem o modificar |
Exemplo quando o site conhece a conta. Os nomes de campo do ERP podem ser diferentes: mapeia a referência da empresa para external_id, a denominação social para company_name, a pessoa para name e o e-mail da conta para email.
widget.init({
external_id: "52",
company_name: "Ricambi Stella S.r.l.",
name: "mario.rossi",
phone: "+393331234567",
email: "amministrazione@ricambi-stella.example",
});
As etiquetas Nome e Telefone do formulário enviam-se como name e phone. O e-mail é email. Não envies Nome, Telefone nem Email.
A primeira chamada abre o chat com nome e telefone e não procura uma empresa. A segunda chega com um e-mail válido. No perfil completo acima, phone é o número do contacto e email fica na empresa só se esta chamada a criar.
Mantém name e company_name separados. Não concatenes a denominação social ao nome de utilizador para obter a associação. Não envies um campo web_code: o nome público é external_id. Não uses o UUID interno da Quoting24 nem companies.code no lugar do valor em web_code.
init(profile) substitui o perfil guardado pelo loader. Não faz merge: passa os campos que queres manter.
Um convidado novo pode começar com pelo menos um nome, um telefone, um e-mail válido ou um external_user_id, ou com uma empresa encontrada ou criada através de external_id. init({}) e company_name sozinho não iniciam uma sessão nova. Se enviares apenas um external_id desconhecido, sem company_name, o widget pede os dados através do formulário.
init({ restoreId: "..." }) não transmite a credencial. O campo é restore_id.
4. Associar ou criar a empresa
O fluxo dispara-se quando a Quoting24 processa a inicialização, mesmo sem uma mensagem:
- Se recebe um
external_idnão vazio, procura uma empresa da mesma organização cujoweb_codeseja igual a esse valor - Se a encontrar, liga o contacto. O nome e o e-mail da empresa existente não mudam
- Se não a encontrar e também receber um
company_namenão vazio, cria a empresa comname = company_name,web_code = external_ide o e-mail se existir, e depois liga o contacto - Se não puder encontrar nem criar a empresa por falta de dados, continua com o perfil disponível. Se não chegar para um convidado novo, pede o formulário. Erros de base de dados ou o limite de pedidos fazem falhar a inicialização
| Dados enviados | Situação na Quoting24 | Resultado |
|---|---|---|
external_id: "52", company_name: "Ricambi Stella S.r.l." |
Existe web_code = "52" |
Usa a empresa existente; não muda o nome |
| Os mesmos dados, mais um e-mail válido | Não existe web_code = "52" |
Cria a empresa com código web 52, denominação social e e-mail, e associa o contacto |
external_id: "52", name: "mario.rossi" |
Nenhuma correspondência | Inicia o convidado sem uma associação de empresa nova |
external_id: "52", sem outros dados |
Existe uma correspondência | Inicia o convidado associado, com o nome predefinido Visitatore |
external_id: "52", sem outros dados |
Nenhuma correspondência | Pede os dados do visitante |
company_name sem external_id, mais name |
Qualquer | Inicia o visitante. Não procura nem cria uma empresa |
A pesquisa não usa denominação social, e-mail, telefone, NIF nem companies.code. "052" e "52" são valores diferentes. Se importares primeiro o ficheiro, preenche web_code com o mesmo valor que o site vai enviar em external_id.
A associação diz respeito ao contacto. Para um contacto já ligado, um código válido novo pode mudar a empresa. Omitir o código, ou enviar um que não se resolve, não apaga a associação anterior. Na mudança de pessoa ou de conta executa reset(): não te limites a mudar o perfil.
Limites
web_code não tem uma restrição de unicidade. Com várias correspondências seleciona-se a primeira, por data de criação crescente. Evita os duplicados: pedidos simultâneos que ainda não encontram a empresa podem criar mais do que uma. O fluxo não garante uma criação atómica e não substitui uma importação ou uma sincronização a partir do ERP.
A criação implícita está limitada a 20 tentativas por janela de 60 segundos e endereço IP, no processo de backend que trata o pedido. Ao ultrapassar, a inicialização falha com TOO_MANY_REQUESTS. Tenta de novo com espera. Evita ciclos contínuos de init().
Os dados chegam do browser e o visitante pode modificá-los. A associação não prova pertença nem identidade e não concede o histórico da empresa. Envia company_name se quiseres permitir esta criação pública. Para atualizar empresas existentes usa a Quoting24 ou uma integração de ERP autorizada.
5. Login, logout e mudança de conta
Mantém uma única instância na estrutura principal de uma SPA. Uma mudança de página não exige um script novo nem um reset.
Na mudança de pessoa ou de conta, e no logout, chama reset() antes do perfil novo. Num recarregamento com o mesmo login ainda ativo, usa init() sem reset, como no arranque: assim conservas a sessão de convidado guardada. O site tem de distinguir o recarregamento da mudança efetiva.
Chamada nas mudanças de autenticação. profile é o perfil do utilizador novo, ou null no logout.
import { loadWidget } from "/assets/quoting24-loader.js";
let identityRevision = 0;
export async function applyWidgetIdentity(profile) {
const revision = ++identityRevision;
const snapshot = profile ? { ...profile } : null;
window.Quoting24Widget?.reset();
if (!snapshot) return;
const widget = await loadWidget();
if (revision !== identityRevision) return;
widget.reset();
widget.init(snapshot);
}
Trata o erro de carregamento na interface do site, com uma nova tentativa. Se recuperas o perfil do backend antes desta função, invalida também esses pedidos na mudança de utilizador. Durante a espera, apaga de imediato o perfil anterior com applyWidgetIdentity(null).
O reset esquece a sessão de convidado desta instância, a credencial guardada, o perfil e o contador de não lidos. Não é uma revogação no servidor e não termina as sessões noutros dispositivos. A mudança de autenticação também tem de ser aplicada nos outros separadores abertos.
Com restauro do histórico
Quando o perfil inclui restore_id, descarrega-o do endpoint autenticado do site depois do reset(), e descarta a resposta se o utilizador mudou entretanto. O recarregamento com o mesmo utilizador reutiliza a credencial já guardada e não deve chamar reset().
import { loadWidget } from "/assets/quoting24-loader.js";
let authRevision = 0;
let pendingRecovery;
// Só no login, no logout e na mudança de utilizador.
// Não a chamar num recarregamento com o mesmo utilizador: reset() descartaria a sessão já em curso.
export async function applyWidgetAuth(user) {
const revision = ++authRevision;
const expectedUserId = user ? String(user.id) : null;
pendingRecovery?.abort();
const controller = new AbortController();
pendingRecovery = controller;
window.Quoting24Widget?.reset();
try {
const widget = await loadWidget();
if (revision !== authRevision) return;
widget.reset();
if (expectedUserId === null) return;
const response = await fetch("/api/widget/session", {
method: "POST",
credentials: "same-origin",
cache: "no-store",
signal: controller.signal,
headers: { "Content-Type": "application/json" },
body: "{}",
});
if (!response.ok) throw new Error("O restauro do widget não está disponível");
const profile = await response.json();
if (revision !== authRevision) return;
if (profile.external_user_id !== expectedUserId || !profile.restore_id) {
throw new Error("Resposta de restauro inesperada");
}
const payload = {
external_user_id: profile.external_user_id,
restore_id: profile.restore_id,
};
for (const key of ["external_id", "company_name", "name", "email", "phone"]) {
if (profile[key]) payload[key] = profile[key];
}
widget.init(payload);
} catch (error) {
if (controller.signal.aborted || revision !== authRevision) return;
console.warn("Chat de assistência: o restauro falhou.", error);
}
}
O ciclo copia só os campos presentes e não vazios. Um e-mail "" ou um ID numérico não devem ser enviados.
API JavaScript
| Método | Uso |
|---|---|
init(profile) |
Fornece ou substitui o perfil atual. O loader não funde os campos com um perfil anterior |
open() / close() / toggle() |
Abre, fecha ou alterna o widget flutuante. Fechar não é um logout. No modo incorporado não têm efeito |
isReady() |
A inicialização da sessão de visitante teve sucesso. Continua falso durante o formulário de contacto |
hasSessionError() |
Erro de sessão assinalado pelo iframe |
reset() |
Apaga perfil, sessão local, credencial de convidado guardada e contador de não lidos |
dispose() |
Desmonta a interface e os listeners. Não apaga a credencial de convidado guardada nem o objeto API global |
Podes ligar um botão do site a window.Quoting24Widget?.open() quando o botão flutuante tiver aparecido. Não esperes por isReady() para um convidado novo abrir o formulário. Não existe um evento público de disponibilidade. Estes indicadores não descrevem todo o estado da rede.
loadWidget() resolvido não significa que o branding e a sessão estejam prontos.
Desmontagem completa
Se tiveres de retirar o widget, executa dispose(). Se a desmontagem coincidir com o logout, executa primeiro reset(). Antes de voltar a carregar o script, elimina o global e repõe a Promise do loader.
export function removeWidget() {
window.Quoting24Widget?.reset();
window.Quoting24Widget?.dispose();
delete window.Quoting24Widget;
document.getElementById("quoting24-widget-script")?.remove();
widgetPromise = undefined;
}
widgetPromise é a definida no módulo do loader. Executa a desmontagem depois de terminar o carregamento. Não a faças ao mesmo tempo que uma nova loadWidget(). Para as mudanças de autenticação usa applyWidgetIdentity() ou applyWidgetAuth(), que esperam pelo loader.
O reset local não revoga restore_id no servidor.
6. Restauro autenticado
A sessão de convidado lê a sua conversa mais recente mesmo depois de a fechar. As conversas visíveis da mesma pessoa noutro browser exigem juntos external_user_id e restore_id.
widget.init({
external_id: "52",
company_name: "Ricambi Stella S.r.l.",
name: "Mario Rossi",
phone: "+393331234567",
email: "mario.rossi@ricambi-stella.example",
external_user_id: "1042",
restore_id: session.restore_id,
});
O restauro não junta sessões de convidado antigas e não partilha o histórico entre colegas da mesma empresa.
Endpoint no backend do site
Exemplo a implementar no portal, não um URL da Quoting24.
{
"external_user_id": "1042",
"external_id": "52",
"company_name": "Ricambi Stella S.r.l.",
"name": "Mario Rossi",
"phone": "+393331234567",
"email": "mario.rossi@ricambi-stella.example",
"restore_id": "<credencial-devolvida-pela-quoting24>"
}
Define Cache-Control: no-store. Identidade e empresa só a partir da sessão do servidor. Aplica a auth e o CSRF da aplicação. Não registes corpos que contenham a credencial.
Se o portal já expõe um endpoint autenticado que devolve o restore_id do utilizador atual para este widget, usa essa resposta e combina-a com o ID da pessoa e a referência da empresa.
Provisioning de servidor para servidor
Caminho típico: /fastify/trpc/widget.provisionRestoreId
(ou /trpc/widget.provisionRestoreId se o backend estiver exposto diretamente).
POST /fastify/trpc/widget.provisionRestoreId
Authorization: Bearer <SEGREDO_INTEGRACAO>
Content-Type: application/json
{
"widget_api_key": "<uuid-publico-do-widget>",
"external_user_id": "1042",
"external_id": "52",
"name": "Mario Rossi",
"phone": "+393331234567",
"email": "mario.rossi@ricambi-stella.example",
"attempt_id": "1042"
}
Os valores gerados começam por q24-restore-v1:. Não os inventes nem os derives de IDs públicos.
| Entrada | Requisito |
|---|---|
widget_api_key |
UUID obrigatório, autorizado para o segredo |
external_user_id |
ID de pessoa estável, igual ao de init() |
attempt_id |
Inteiro positivo estritamente crescente, como cadeia decimal, máximo 9223372036854775807 |
saved_restore_id |
Credencial já guardada. Omite-a no primeiro provisioning |
external_id, name, email, phone |
Facultativos, com os mesmos limites que init() |
Padrão ensure, recupera ou cria:
- Autentica o utilizador e adquire um bloqueio sobre a linha de restauro, numa transação
- Atribui um
attempt_idnovo a partir de uma sequência depois do bloqueio (CACHE 1, não cíclica; a sequência sobrevive ao rollback) - Chama a Quoting24 com
attempt_ide, se existir,saved_restore_id - Guarda
result.data.restore_ide faz commit - Só depois do commit devolve a credencial ao browser
Um saved_restore_id válido tem de ser reutilizado. Omiti-lo quando já existe uma chave válida substitui a chave e invalida as sessões restauradas. Não geres uma chave nova em cada visualização de página.
Se o provisioning remoto tiver sucesso e a gravação local falhar, devolve um erro. Não devolvas a chave não guardada. Tempos limite reduzidos.
company_name envia-se em init(). O corpo de provisioning acima não o inclui: a associação e a criação da empresa, descritas na secção 4, ocorrem quando o browser inicializa a sessão. O e-mail da inicialização pública só é escrito numa empresa nova.
Resultado no widget
A Quoting24 verifica a credencial e carrega o histórico do widget visível desse utilizador. Os chats já fechados podem aparecer como conversa anterior: consultá-los não reabre a conversa antiga; o envio fica na conversa atual.
Uma credencial inválida, de outro utilizador ou revogada é rejeitada, sem conversão automática em sessão de convidado. O botão «Riprova» do widget não pede por si só um restore_id novo. A interface atual está em italiano.
7. Página, rede e aspeto
- Uma só instância por página
- CSP. Permite a origem do widget em
script-srceframe-src, e a origem de API indicada para a instalação emconnect-src(carregamento das definições gráficas). Autoriza as imagens configuradas emimg-src. O loader insere estilos em linha e não expõe uma opção pública de nonce: verifica a CSP do site. O CSS da página não modifica o interior do iframe de outra origem - Ligações do iframe. Tem de alcançar a API, Realtime/WebSocket e o armazenamento da própria instalação. Para filtros de rede usa os hosts efetivos do ambiente
- Aspeto e idioma. Logótipo, cor, posição e mensagens configuram-se na Quoting24.
company_nameé a denominação social do cliente. Não existe um parâmetro públicolanguageoulocale; a interface atual está em italiano - Telemóvel. O chat flutuante pode ocupar o viewport. No modo incorporado o site controla altura e largura
- Anexos e horários. PDF, DOC/DOCX, JPG/JPEG e PNG. Até 10 MiB por ficheiro e 100 MiB de anexos referenciados por conversa. Fora de horário o widget mostra o banner e deixa disponível a composição
8. Verificar a integração
Usa uma conta e um canal de teste. Confirma o ficheiro na Quoting24, além do chat.
| Prova | Resultado esperado |
|---|---|
| Browser novo, só script | Formulário de contacto, depois conversa |
Payload completo com external_id já presente como web_code |
Contacto associado à empresa existente; nome e e-mail da empresa inalterados |
| Payload completo com código novo | Empresa criada com código web, denominação social e e-mail; contacto associado |
Código novo e nome, sem company_name |
Chat iniciado, nenhuma empresa criada |
| Só código novo | Formulário de contacto |
| Só denominação social e nome | Nenhuma empresa procurada ou criada por nome |
| E-mail vazio, ID numérico ou denominação social demasiado longa | Payload rejeitado |
Segundo init() na mesma sessão |
Continua a sessão de convidado. O e-mail e o telefone não recuperam o chat de outra pessoa |
| Recarregamento, navegação SPA, telemóvel | Uma só instância; continuidade de convidado se houver armazenamento; layout utilizável |
| Logout ou mudança de conta durante o carregamento | Sessão anterior apagada; os resultados tardios não restauram o perfil antigo |
O mesmo utilizador autenticado, outro browser, restore_id válido |
Histórico dessa pessoa |
| Dois utilizadores da mesma empresa | Credenciais e históricos distintos |
| Convidado e depois restauro | reset() pelo meio; as sessões não se juntam |
| Credencial inválida ou revogada | Erro, sem recurso automático a convidado |
| Conversas anteriores | Só consulta; o envio fica na atual |
| Domínio não autorizado | Acesso rejeitado |
| Rede interrompida | Reconexão; rascunho conservado se o envio falhar |
Resolução de problemas
| Sintoma | Verificação |
|---|---|
| O botão não aparece | URL, chave, canal ativo, domínios, CSP, pedido widget.getBranding. O só global JavaScript não prova que o botão exista. open() não tem efeito até o branding carregar, e não tem efeito no modo incorporado |
Quoting24Widget não está definido |
Espera pelo carregamento. Verifica o parâmetro key |
| O chat incorporado é invisível | Contentor antes do loader, ID correto, altura maior do que zero |
Domain not allowed |
Nome de host do site, sem esquema, porta nem caminho. Verifica também os testes. A página tem de ser servida por HTTP |
| Formulário de telefone apesar do perfil | Ordem assíncrona, snake_case, valores vazios, reset() anterior, perfil insuficiente para um convidado novo |
| A empresa não é encontrada | Compara external_id com web_code na mesma organização, espaços e zeros iniciais incluídos |
| A empresa em falta não é criada | São precisos external_id e company_name ambos não vazios. Verifica a validação e o limite de criação |
| O nome ou o e-mail da empresa não mudam | Previsto: init() cria as empresas em falta e não atualiza as existentes |
| O e-mail não aparece no contacto | Previsto na inicialização pública: o e-mail só se usa ao criar a empresa |
TOO_MANY_REQUESTS |
Espera e tenta de novo. Evita chamadas repetidas ou importações em massa através do widget |
| Falta o histórico noutro dispositivo | O payload de empresa não o restaura. São precisos external_user_id e restore_id |
| Sessões restauradas invalidadas | As chamadas ensure têm de reenviar saved_restore_id. Não emitas uma chave nova em cada visualização de página |
| «Riprova» não chega | Um restore_id novo a partir do teu endpoint, depois reset() e init() |
| Depois de uma mudança de conta fica o chat antigo | reset() de imediato na mudança de identidade. Descarta as respostas assíncronas anteriores |
Depois de dispose() o script não recria o widget |
Elimina o global e o script, repõe a Promise do loader e volta a carregar |
| A interface continua em italiano | Nenhuma opção pública language ou locale no loader |
Referências de implementação
- Loader e API JavaScript:
@frontend/src/public/widget/script.ts - Inicialização, associação de empresa e criação implícita:
@backend/fastify/routes/widget/initialize.ts - Testes de inicialização e empresas:
@backend/fastify/__tests__/widget/widgetInitialize.test.ts - Domínio e canal:
@backend/fastify/routes/widget/procedures/publicWidgetProcedure.ts - Provisioning de
restore_id:@backend/fastify/routes/widget/provisionRestoreId.ts