Assinatura Incorporada (Embedded / iframe) na Sua Aplicação via API

Redirecionar o usuário para uma página externa de assinatura funciona, mas quebra a experiência do seu produto. A assinatura incorporada (embedded signing) resolve isso: o checkout de assinatura é aberto de dentro da sua aplicação, pelo SDK oficial, mantendo o usuário no seu contexto e no seu fluxo. Este guia mostra, passo a passo, como criar a signing session, obter o clientSecret, abrir o checkout com o SDK @signdocs-brasil/js, tratar os callbacks de conclusão, confirmar tudo pelo webhook — e por que o modelo de popup do SignDocs substitui o iframe clássico que dá nome a esta página.

O ponto de partida é a mesma chamada da Assinatura Expressa: uma única requisição POST /v1/signing-sessions. A diferença entre checkout hospedado e embedded não está em endpoints distintos, e sim em onde você apresenta a URL devolvida. Este artigo é parte do cluster prático da nossa API de assinatura digital.

Vamos do conceito ao código: requisição, abertura segura do checkout (com a regra crítica do clientSecret), os callbacks de evento, a finalização e a confirmação server-to-server. A criptografia profunda (PAdES, ICP-Brasil, evidence pack) é tratada na página da Expressa; aqui o foco é a experiência incorporada.

Embedded vs. redirecionamento: quando incorporar faz sentido

Existem dois jeitos de levar um signatário até a tela de assinatura a partir da sua aplicação. No redirecionamento (checkout hospedado), você manda o usuário para uma página hospedada pela SignDocs, em outro domínio, e o traz de volta no fim. Na assinatura incorporada, essa mesma tela é aberta pelo SDK a partir da sua própria página — um checkout em popup controlado pela sua aplicação — e o usuário não abandona a jornada do seu produto.

Critério Checkout hospedado (redirect) Assinatura incorporada (embedded)
Onde o usuário assina Página hospedada, em outro domínio A partir do seu app, em popup controlado por ele
Branding / UX Personalização limitada à página hospedada Controle total do layout ao redor do widget
Esforço de integração Mínimo: pegar a URL e redirecionar Baixo: chamar o SDK + tratar callbacks
Continuidade de sessão Usuário sai e volta para o seu app Usuário permanece na mesma página
Confirmação do resultado Webhook + URL de retorno Webhook + callbacks do SDK (UX)
Ideal para Fluxos simples, e-mails, links avulsos SaaS, dashboards, jornadas de produto
Regra de ouro: incorporada e hospedada não são endpoints diferentes — ambas nascem do mesmo POST /v1/signing-sessions. Escolha embedded quando a assinatura faz parte de uma jornada dentro do seu produto e você não quer que o usuário perceba que saiu dele.

Passo 1 — Criar a signing session

Tudo começa com uma chamada autenticada à API. Você precisa de um bearer token obtido via OAuth2 client-credentials — veja o detalhamento em autenticação OAuth2. Em homologação, use o host api-hml.signdocs.com.br; lembre que entidades de HML têm TTL de 7 dias.

A requisição declara o documento, o signatário e a política de autenticação (profile). No exemplo abaixo, criamos uma sessão de assinatura simples com aceite (clickwrap) e desafio OTP por e-mail:

# Passo 1: criar a signing session (HML) curl -X POST https://api-hml.signdocs.com.br/v1/signing-sessions \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "purpose": "DOCUMENT_SIGNATURE", "policy": { "profile": "CLICK_PLUS_OTP" }, "signer": { "name": "Maria Silva", "email": "maria@empresa.com.br", "userExternalId": "usr_8842", "cpf": "12345678901" }, "document": { "content": "JVBERi0xLjcK...", "filename": "Contrato_Prestacao_Servicos.pdf" }, "metadata": { "internal_ref": "deal_8842" } }'

Repare que não há nenhum campo de "modo": a mesma sessão serve para o checkout hospedado e para o embed — a diferença está só em como você apresenta o resultado no passo 3. Note também que policy.profile aceita valores como CLICK_PLUS_OTP ou DIGITAL_CERTIFICATE (certificado ICP-Brasil) — e nunca DIGITAL_SIGN_A1, que existe apenas como step.type na resposta.

Passo 2 — Obter a embed URL e o clientSecret

A resposta da API traz a sessão criada. Dois campos são essenciais: url (a página de assinatura hospedada) e clientSecret (a credencial daquela sessão — é ela que o SDK usa para abrir o checkout):

// Resposta de POST /v1/signing-sessions { "sessionId": "01JC9M7P9R1T3V5X7Z9B1D3F5H", "transactionId": "01JC9M7P9R1T3V5X7Z9B1D3F5J", "status": "ACTIVE", "url": "https://sign.signdocs.com.br/s/01JC9M7P9R1T3V5X7Z9B1D3F5H", "clientSecret": "ss_secret_9d8c7b6a5f4e3d2c1b0a", "expiresAt": "2026-06-27T14:00:00Z" }
Atenção — a regra mais importante deste artigo: o campo url sozinho não é o link de assinatura. No modo hospedado, o link final é url + "?cs=" + clientSecret; no embed, é o clientSecret que você entrega ao SDK. Sem ele, a sessão não autentica e a tela de assinatura não carrega.

Em código, a montagem fica assim:

// Hospedado: montar o link combinando url + clientSecret const { url, clientSecret } = session; // da resposta da API const linkHospedado = `${url}?cs=${encodeURIComponent(clientSecret)}`; // Embed: o clientSecret vai direto para o SDK (próximo passo)

O clientSecret é sensível: trate-o como segredo, não o registre em logs públicos nem o exponha em URLs compartilháveis. Ele só deve viajar do seu backend até o navegador do signatário no momento de abrir o checkout.

Passo 3 — Abrir o checkout com o SDK (com a guarda de segurança do remetente)

Antes de abrir o checkout, há uma checagem de segurança que você não pode pular. O clientSecret dá acesso de assinatura àquela sessão. Se a sua aplicação abrir o checkout automaticamente para quem está enviando o documento, o remetente acabará assinando no lugar do signatário — destruindo a validade jurídica da prova.

Guarda obrigatória: só abra o checkout de assinatura quando o usuário logado for de fato o signatário. Na prática, gate em signerEmail === senderEmail. Se forem diferentes, não abra a sessão no navegador do remetente — entregue o link ao signatário correto por e-mail ou outro canal. Esse padrão vale para qualquer integração onde quem cria a sessão pode não ser quem assina.

Com a guarda passada, abrir o checkout é direto — instale o SDK (npm install @signdocs-brasil/js) e chame checkout() em resposta a um clique do usuário:

// Passo 3: abrir o checkout — apenas se o usuário logado é o signatário import { SignDocsBrasil } from '@signdocs-brasil/js'; const sd = SignDocsBrasil.init({ locale: 'pt-BR' }); function abrirCheckout(session, signerEmail, currentUserEmail) { // GUARDA: nunca auto-abrir a sessão para o remetente if (signerEmail.toLowerCase() !== currentUserEmail.toLowerCase()) { // Quem está logado NÃO é o signatário: entregue o link por e-mail. console.warn('Usuário logado não é o signatário — checkout não será aberto aqui.'); return; } // Abrir SEMPRE em resposta a um gesto do usuário (clique), // para não esbarrar no bloqueador de popups do navegador const handle = sd.checkout({ clientSecret: session.clientSecret, onComplete: (evt) => mostrarSucesso(evt.evidenceId), onError: (err) => mostrarErro(err.message), onClose: () => registrarAbandono(), }); return handle; // handle.close() encerra o checkout programaticamente }

O popup roda no domínio seguro da SignDocs, o que resolve sozinho as permissões de câmera e microfone quando o perfil usa biometria facial — sem atributos allow nem ajustes de Content-Security-Policy do seu lado. Duas atenções práticas: chame checkout() em resposta direta a um clique (bloqueadores de popup barram aberturas espontâneas) e sirva sua aplicação sobre HTTPS.

E o iframe? Por que o embed é um popup

Se você chegou aqui procurando "assinatura por iframe": o SDK do SignDocs já ofereceu um mount() em elemento da página, hoje depreciado — internamente ele também abre o popup. A razão é técnica: dentro de um iframe cross-origin, as permissões de câmera/microfone exigidas pela biometria dependem de atributos e políticas frágeis, e as proteções modernas contra clickjacking tornam o embed em moldura cada vez mais restrito. O popup preserva o mesmo efeito de produto — o usuário não perde a sua página — com um contexto seguro de primeira classe. E a ponte postMessage que você implementaria à mão no modelo iframe existe por baixo do SDK (eventos como SIGNDOCS_READY, SIGNDOCS_STEP_CHANGE, SIGNDOCS_COMPLETE), só que já validada e abstraída em callbacks.

Passo 4 — Tratar os callbacks do checkout

O checkout conversa com a sua página pelos callbacks do SDK (que encapsulam a ponte postMessage do navegador, incluindo a validação de origem). Conforme o usuário avança, sua aplicação reage para atualizar a UX — exibir uma tela de sucesso, registrar abandono, oferecer nova tentativa.

Callbacks do checkout

Callback Quando ocorre Reação sugerida na UX
onComplete(evt) O signatário concluiu a assinatura — evt.evidenceId identifica a evidência Exibir sucesso; aguardar confirmação por webhook
onError(err) Erro durante o fluxo (sessão expirada, falha de rede) — err.message/err.code Exibir erro; oferecer recriar a sessão
onClose() O signatário fechou o checkout sem concluir Registrar abandono; manter o botão "assinar" disponível

Na prática, os callbacks já chegam validados pelo SDK — sem listener manual nem checagem de event.origin do seu lado:

// Passo 4: reagir aos callbacks do checkout const handle = sd.checkout({ clientSecret: session.clientSecret, onComplete: ({ evidenceId }) => { // Sinal de UX apenas — NÃO confie nele como prova. showSuccessScreen(evidenceId); // A confirmação definitiva chega pelo webhook (próxima seção) }, onError: ({ message, code }) => { showErrorScreen(message, code); }, onClose: () => { registrarAbandono(); // usuário fechou sem concluir }, });
Callback é sinal de UX, não fonte de verdade. Eventos do navegador podem ser perdidos (usuário fecha a aba) ou atrasados. Use-os para reagir visualmente, mas confirme o estado final da assinatura sempre pelo webhook server-to-server. Nunca libere um pedido, contrato ou pagamento só com base no onComplete do checkout.

Passo 5 — Finalização e confirmação por webhook

Quando o signatário conclui todos os passos no checkout, a SignDocs finaliza a transação do lado servidor: gera o PDF assinado (PAdES, nível baseline), registra os carimbos de hora do servidor (timestamps ISO-8601 gravados pelos servidores da SignDocs) e monta o pacote de evidências. A sua aplicação fica sabendo disso de forma confiável pelo webhook — o mesmo mecanismo descrito em webhooks e eventos da API de assinatura.

O fluxo de confirmação combina os dois canais: o onComplete dá um feedback imediato ao usuário; o webhook confirma o estado de forma auditável e dispara as ações de negócio (faturamento, atualização de CRM, liberação de acesso).

// Webhook recebido após a conclusão no checkout { "id": "01JC9M8Q0S2U4W6Y8A0C2E4G6J", "eventType": "SIGNING_SESSION.COMPLETED", "tenantId": "ten_a1b2c3", "transactionId": "01JC9M7P9R1T3V5X7Z9B1D3F5J", "timestamp": "2026-06-24T14:08:21Z", "data": { "status": "COMPLETED" } }

Valide a assinatura HMAC-SHA256 do webhook (cabeçalho X-SignDocs-Signature), aplique idempotência pelo id do evento e só então execute a lógica de negócio. O payload não carrega os seus metadados: use o transactionId para buscar a transação (GET /v1/transactions/{id}, que devolve o metadata.internal_ref enviado na criação) e casar o evento com o registro correto no seu sistema. Dali você baixa o PDF assinado e o pacote .p7m (via GET .../evidence) — e qualquer parte pode validar publicamente em verificador.signdocs.com.br.

Métodos de autenticação dentro do checkout

Uma dúvida comum é se o embedding limita os métodos de assinatura. Não limita: os métodos são definidos no profile e nos steps da signing session, independentemente de a tela ser aberta por link hospedado ou pelo SDK. Todos os fluxos rodam normalmente no checkout:

  • CLICK_ACCEPT — aceite eletrônico (clickwrap), com registro de consentimento.
  • OTP_CHALLENGE — código de uso único por SMS ou e-mail.
  • BIOMETRIC_LIVENESS / BIOMETRIC_MATCH — prova de vida e conferência facial (o popup cuida das permissões de câmera sozinho).
  • DIGITAL_SIGN_A1 — assinatura com certificado ICP-Brasil A1 (em arquivo; titulares de token A3 usam o assinador desktop do aplicativo).

Esses passos podem ser combinados em perfis (por exemplo, biometria + OTP). O detalhamento de cada método, das combinações possíveis e de como o nível probatório muda está no guia de autenticação multimétodo na API de assinatura. A camada criptográfica (PAdES/CAdES, PKCS#7/CMS, hash SHA-256 e cadeia de certificados ICP-Brasil) é a mesma da assinatura padrão e está coberta na página da Assinatura Expressa.

Boas práticas e armadilhas comuns no embed

Erros frequentes

  • Esquecer o clientSecret: usar só o campo url faz a tela não carregar. No hospedado, combine url + ?cs=; no embed, entregue o secret ao SDK.
  • Auto-abrir para o remetente: sem a guarda signerEmail === senderEmail, quem envia assina no lugar de quem deveria.
  • Confiar só no callback: tratar onComplete como prova final, em vez do webhook server-to-server.
  • Abrir o checkout fora de um clique: chamadas espontâneas a checkout() esbarram no bloqueador de popups — sempre abra em resposta a um gesto do usuário.
  • Logar o clientSecret: ele é sensível; vazá-lo em logs ou URLs compartilhadas é um risco.
  • Confundir sessões: o clientSecret pertence a uma sessão específica; expirou (SIGNING_SESSION.EXPIRED)? Crie uma nova sessão.

Recomendações

  • Desabilite o botão "assinar" enquanto o checkout está aberto e reative-o no onClose.
  • Guarde o handle retornado por checkout()handle.close() encerra o fluxo programaticamente se a sua tela mudar.
  • Sirva sua aplicação sobre HTTPS; o checkout roda no domínio seguro da SignDocs.
  • Para enviar documentos a terceiros (sem embed), use o checkout hospedado — mesmo endpoint, entrega por link.
  • Em HML, lembre do TTL de 7 dias das entidades ao testar fluxos longos.
SignDocs: assinatura nativa dentro do seu produto. Com a Assinatura Expressa você cria a sessão em uma chamada e abre a tela completa de assinatura — ICP-Brasil, OTP e biometria — de dentro do seu app, sem tirar o usuário da jornada. Fale com nossa equipe para receber as credenciais do sandbox gratuito — o acesso à API é um plano sob medida.

Perguntas Frequentes

Qual a diferença entre assinatura incorporada (embedded) e checkout hospedado?

No checkout hospedado, você redireciona o signatário para uma página de assinatura totalmente hospedada pela SignDocs, em outro domínio. Na assinatura incorporada (embedded), o checkout é aberto de dentro da sua própria aplicação pelo SDK oficial @signdocs-brasil/js — um popup controlado pelo seu app, com callbacks de conclusão — mantendo o usuário na sua jornada. Ambos partem da mesma chamada POST /v1/signing-sessions; o que muda é como o resultado é apresentado: redirecionar para url?cs=clientSecret, ou entregar o clientSecret ao SDK. O embedded é ideal quando a assinatura faz parte de uma jornada dentro do produto; o hospedado, quando o link viaja por e-mail ou WhatsApp.

Como monto a URL de assinatura para usar no iframe?

A URL retornada no campo url da signing session NÃO é o link final por si só. No modo hospedado, combine-a com o clientSecret como parâmetro de query: url + '?cs=' + clientSecret. No embed, você nem monta URL — entrega o clientSecret diretamente ao SDK (sd.checkout({clientSecret})), que abre o checkout. Em ambos os casos, sem o clientSecret a sessão não autentica e a assinatura não carrega; trate-o como segredo e nunca o exponha em logs públicos.

Como minha aplicação sabe quando o usuário terminou de assinar dentro do iframe?

Pelos callbacks do SDK: onComplete (com o evidenceId da assinatura), onError (mensagem e código) e onClose (usuário fechou sem concluir). A ponte postMessage do navegador existe por baixo, mas o SDK a valida e abstrai — você não registra listener nem confere event.origin manualmente. Para confirmação definitiva e auditável, porém, nunca confie só no callback do navegador: trate-o como sinal de UX e confirme o estado final pelo webhook HTTPS server-to-server, que é a fonte de verdade.

Por que não devo abrir automaticamente a URL de assinatura para o remetente?

O clientSecret da signing session dá acesso de assinatura àquela sessão. Se a sua aplicação abrir o checkout automaticamente para quem está enviando o documento, o remetente acabaria assinando no lugar do signatário, corrompendo a prova jurídica. A regra de segurança é gatear a abertura: só chame o checkout quando signerEmail for igual a senderEmail (ou seja, quando o próprio usuário logado é o signatário). Caso contrário, entregue o link ao signatário correto por e-mail ou outro canal.

Preciso de algum cabeçalho especial para permitir o iframe?

Não. Como o checkout abre em popup no domínio seguro da SignDocs — e não em um iframe na sua página — você não precisa ajustar Content-Security-Policy nem cabeçalhos de frame do seu lado. Garanta apenas HTTPS na sua aplicação e abra o checkout em resposta a um clique do usuário, para não esbarrar no bloqueador de popups. Em ambiente de homologação, use o host api-hml.signdocs.com.br e lembre que as entidades de HML têm TTL de 7 dias.

O embedded suporta certificado ICP-Brasil e biometria dentro do iframe?

Sim. Os métodos de autenticação são definidos no profile e nos steps da signing session, não na forma de apresentação. Perfis como CLICK_ONLY, CLICK_PLUS_OTP, BIOMETRIC/BIOMETRIC_PLUS_OTP e DIGITAL_CERTIFICATE (certificado ICP-Brasil A1; token A3 usa o assinador desktop do aplicativo) rodam todos no checkout aberto pelo SDK — o popup cuida sozinho das permissões de câmera para a biometria. O detalhamento de cada perfil está na seção de métodos de autenticação de o que é uma API de assinatura digital.

Incorpore a assinatura digital direto no seu app

Crie a signing session em uma chamada, abra o checkout com o SDK oficial usando o clientSecret e confirme tudo por webhook — ICP-Brasil, OTP e biometria, sem tirar o usuário do seu produto. O acesso à API é um plano sob medida, com sandbox de homologação gratuito.

Fale com o time comercial Fale com nossa equipe sobre a API