Guias de Desenvolvimento

Signing Sessions

Guias dos SDKs

Assinatura Expressa — Visão Geral

Integre assinatura eletrônica em 1 chamada de API. Escolha o perfil de verificação ideal para seu caso de uso.


1. O que é Assinatura Expressa

Assinatura Expressa (Signing Sessions) encapsula todo o fluxo de assinatura em uma única chamada de API. Em vez de 8+ chamadas sequenciais (criar transação, upload, listar etapas, iniciar, concluir, finalizar, verificar), você cria uma sessão e uma página hospedada cuida de tudo.

A API oferece 8 perfis de política para diferentes níveis de verificação — de um simples aceite por clique até biometria facial contra a base do SERPRO. Cada perfil configura automaticamente as etapas necessárias, sem que você precise orquestrá-las manualmente.


2. Tabela comparativa de perfis

Perfil Descrição Campos obrigatórios Caso de uso
CLICK_ONLY Aceite simples por clique name, cpf ou cnpj Termos de uso, contratos simples
CLICK_PLUS_OTP Aceite + código OTP email (ou phone) Contratos com confirmação
BIOMETRIC Verificação facial cpf Verificação de identidade
BIOMETRIC_PLUS_OTP Biometria + OTP cpf + email Alta segurança
DIGITAL_CERTIFICATE Certificado ICP-Brasil A1 cpf Assinatura digital qualificada
BIOMETRIC_SERPRO Biometria contra base SERPRO cpf + birthDate Verificação governamental
BIOMETRIC_SERPRO_AUTO_FALLBACK NT65 consignado cpf + birthDate INSS consignado (NT65)
CUSTOM Etapas customizadas varia Integrações avançadas

3. Visão geral do fluxo

O diagrama abaixo ilustra o fluxo completo, válido para todos os perfis:

SEU FRONTEND                    SEU BACKEND                      SIGNDOCS API
─────────────                    ────────────                     ────────────

Usuário clica "Assinar"
       │
       ├──> POST /api/sign ──────>
                                  Cria sessão                ──> POST /v1/signing-sessions
                                  (perfil, signatário, PDF)       Cria transação + etapas
                                                             <── { sessionId, clientSecret }
                                  <── { clientSecret }
       <── { clientSecret }
       │
       sd.checkout({ clientSecret })  ──────────────────────> Página hospedada
                                                               Adapta UI ao perfil
                                                               Etapas completadas
       <── onComplete(evidenceId)                              Auto-finalização
       │
       ├──> Verificar backend ───>
                                  Poll status OU webhook
                                  GET /v1/verify/{evidenceId}

4. Configuração inicial

Estes passos são realizados uma única vez e compartilhados por todos os perfis.

4.1 Variáveis de ambiente

Todas as chamadas assumem estas variáveis configuradas:

export SIGNDOCS_CLIENT_ID="your_client_id"
export SIGNDOCS_CLIENT_SECRET="your_client_secret"
export SIGNDOCS_BASE_URL="https://api-hml.signdocs.com.br"   # HML
# export SIGNDOCS_BASE_URL="https://api.signdocs.com.br"     # Produção

HML vs Produção

HML (Sandbox) Produção
Base URL api-hml.signdocs.com.br api.signdocs.com.br
Signing Domain sign-hml.signdocs.com.br sign.signdocs.com.br
Dados reais? Não Sim

4.2 Instalar o SDK do backend

npm install @signdocs-brasil/api
import { SignDocsBrasilClient } from '@signdocs-brasil/api';

const client = new SignDocsBrasilClient({
  clientId: process.env.SIGNDOCS_CLIENT_ID!,
  clientSecret: process.env.SIGNDOCS_CLIENT_SECRET!,
  baseUrl: process.env.SIGNDOCS_BASE_URL,
});
pip install signdocs-brasil
import os
from signdocs_brasil import SignDocsBrasilClient, ClientConfig

client = SignDocsBrasilClient(ClientConfig(
    client_id=os.environ['SIGNDOCS_CLIENT_ID'],
    client_secret=os.environ['SIGNDOCS_CLIENT_SECRET'],
    base_url=os.environ.get('SIGNDOCS_BASE_URL', 'https://api-hml.signdocs.com.br'),
))
go get github.com/signdocsbrasil/signdocsbrasil-go
import signdocs "github.com/signdocsbrasil/signdocsbrasil-go"

client, err := signdocs.NewClient(
    signdocs.WithCredentials(os.Getenv("SIGNDOCS_CLIENT_ID"), os.Getenv("SIGNDOCS_CLIENT_SECRET")),
    signdocs.WithBaseURL(os.Getenv("SIGNDOCS_BASE_URL")),
)
if err != nil { log.Fatal(err) }
<dependency>
  <groupId>com.signdocsbrasil</groupId>
  <artifactId>signdocsbrasil-api</artifactId>
  <version>1.0.0</version>
</dependency>
import com.signdocsbrasil.SignDocsBrasilClient;

SignDocsBrasilClient client = SignDocsBrasilClient.builder()
    .clientId(System.getenv("SIGNDOCS_CLIENT_ID"))
    .clientSecret(System.getenv("SIGNDOCS_CLIENT_SECRET"))
    .baseUrl(System.getenv("SIGNDOCS_BASE_URL"))
    .build();
composer require signdocs-brasil/signdocs-brasil-php
use SignDocsBrasil\Api\SignDocsBrasilClient;

$client = new SignDocsBrasilClient([
    'clientId' => getenv('SIGNDOCS_CLIENT_ID'),
    'clientSecret' => getenv('SIGNDOCS_CLIENT_SECRET'),
    'baseUrl' => getenv('SIGNDOCS_BASE_URL') ?: 'https://api-hml.signdocs.com.br',
]);
dotnet add package SignDocsBrasil.Api
using SignDocsBrasil.Api;

var client = SignDocsBrasilClient.CreateBuilder()
    .ClientId(Environment.GetEnvironmentVariable("SIGNDOCS_CLIENT_ID")!)
    .ClientSecret(Environment.GetEnvironmentVariable("SIGNDOCS_CLIENT_SECRET")!)
    .BaseUrl(Environment.GetEnvironmentVariable("SIGNDOCS_BASE_URL")
        ?? "https://api-hml.signdocs.com.br")
    .Build();

4.3 Preparar codificação do PDF

Todos os perfis exigem o PDF codificado em base64. Veja como ler e codificar:

import { readFileSync } from 'fs';

const pdfBase64 = readFileSync('contrato.pdf').toString('base64');
import base64
from pathlib import Path

pdf_base64 = base64.b64encode(Path('contrato.pdf').read_bytes()).decode()
import (
    "encoding/base64"
    "os"
)

pdfBytes, err := os.ReadFile("contrato.pdf")
if err != nil { log.Fatal(err) }
pdfBase64 := base64.StdEncoding.EncodeToString(pdfBytes)
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;

byte[] pdfBytes = Files.readAllBytes(Path.of("contrato.pdf"));
String pdfBase64 = Base64.getEncoder().encodeToString(pdfBytes);
$pdfBase64 = base64_encode(file_get_contents('contrato.pdf'));
var pdfBase64 = Convert.ToBase64String(File.ReadAllBytes("contrato.pdf"));

5. Frontend: Abrir a página de assinatura

O widget JavaScript é idêntico para todos os perfis. A página hospedada adapta a interface automaticamente com base no perfil escolhido no backend.

Via CDN

<script src="https://cdn.signdocs.com.br/v1/signdocs-brasil.js"></script>

Via npm

npm install @signdocs-brasil/js

Template HTML mínimo

Este template funciona para todos os perfis. O frontend não precisa saber qual perfil foi escolhido — a página hospedada adapta a UI automaticamente.

<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <title>Assinar Documento</title>
</head>
<body>
  <h1>Assinatura de Contrato</h1>
  <button id="btn-assinar">Assinar agora</button>

  <script src="https://cdn.signdocs.com.br/v1/signdocs-brasil.js"></script>
  <script>
    const sd = SignDocsBrasil.init({ locale: 'pt-BR' });

    document.getElementById('btn-assinar').addEventListener('click', async () => {
      // 1. Seu backend cria a sessão e retorna o clientSecret
      const res = await fetch('/api/signing-session', { method: 'POST' });
      const { clientSecret } = await res.json();

      // 2. Abre o popup de assinatura
      sd.checkout({
        clientSecret,
        onComplete(event) {
          alert('Assinatura concluída! Evidence ID: ' + event.evidenceId);
        },
        onError(event) {
          console.error('Erro:', event.message, event.code);
        },
        onClose() {
          console.log('Popup fechado — verifique o status no backend');
        },
      });
    });
  </script>
</body>
</html>

Popup bloqueado? Chame sd.checkout() dentro de um handler de click do usuário (como acima). Navegadores bloqueiam popups fora de interações diretas.

Alternativa: Redirecionamento

Se preferir redirecionar o usuário em vez de abrir um popup, use a URL da sessão diretamente:

<a href="{{ session.url }}">Assinar documento</a>

6. Monitorar conclusão

Opção A — Polling

Use o método waitForCompletion do SDK para aguardar a conclusão da sessão:

const result = await client.signingSessions.waitForCompletion(session.sessionId, {
  timeoutMs: 300_000,
  intervalMs: 2_000,
});
console.log(result.status);      // 'COMPLETED'
console.log(result.evidenceId);  // 'ev_ghi789'
result = client.signing_sessions.wait_for_completion(
    session.session_id,
    timeout_ms=300_000,
    interval_ms=2_000,
)
print(result.status)       # COMPLETED
print(result.evidence_id)
result, err := client.SigningSessions.WaitForCompletion(ctx, session.SessionID,
    signdocs.WithTimeout(300*time.Second),
    signdocs.WithInterval(2*time.Second),
)
if err != nil { log.Fatal(err) }
fmt.Println("Status:", result.Status)
fmt.Println("Evidence ID:", result.EvidenceID)
SigningSessionStatusResponse result = client.signingSessions()
    .waitForCompletion(session.sessionId, 300_000, 2_000);
System.out.println("Status: " + result.status);
System.out.println("Evidence ID: " + result.evidenceId);
$result = $client->signingSessions->waitForCompletion(
    $session->sessionId, ['timeoutMs' => 300000, 'intervalMs' => 2000]
);
echo "Status: " . $result->status . "\n";
echo "Evidence ID: " . $result->evidenceId . "\n";
var result = await client.SigningSessions.WaitForCompletionAsync(
    session.SessionId, timeoutMs: 300_000, intervalMs: 2_000);
Console.WriteLine($"Status: {result.Status}");
Console.WriteLine($"Evidence ID: {result.EvidenceId}");

Opção B — Webhook (recomendado para produção)

Em produção, use webhooks em vez de polling para evitar chamadas desnecessárias.

Registrar o webhook

curl -X POST "$SIGNDOCS_BASE_URL/v1/webhooks" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/signdocs",
    "events": ["signing_session.completed", "signing_session.failed"],
    "secret": "whsec_your_webhook_secret"
  }'

Payload recebido

{
  "event": "signing_session.completed",
  "sessionId": "ss_abc123",
  "transactionId": "tx_def456",
  "evidenceId": "ev_ghi789",
  "profile": "CLICK_ONLY",
  "completedAt": "2026-03-11T14:30:00Z",
  "signer": {
    "name": "Maria da Silva",
    "cpf": "123.456.789-00"
  }
}

Verificação de assinatura: Valide o header X-SignDocs-Signature usando HMAC-SHA256 com o secret configurado no webhook. Rejeite requisições com assinatura inválida para evitar ataques de replay.


7. Recuperar evidência

Verificação pública (sem autenticação)

Qualquer pessoa com o evidenceId pode verificar a autenticidade da assinatura:

curl "$SIGNDOCS_BASE_URL/v1/verify/{evidenceId}"

Retorna os dados da evidência (signatário, timestamp, hash do documento) sem exigir autenticação.

Download do pacote .p7m (com autenticação)

Para baixar o pacote de evidência completo (.p7m com assinatura ICP-Brasil), use:

curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  "$SIGNDOCS_BASE_URL/v1/transactions/{transactionId}/evidence" \
  -o evidencia.p7m

O arquivo .p7m contém o JSON de evidência assinado criptograficamente, verificável pelo Validador ITI.


8. Personalizar aparência

Customize a página hospedada para combinar com a identidade visual da sua marca. Passe o objeto appearance ao criar a sessão:

const session = await client.signingSessions.create({
  profile: 'CLICK_ONLY',
  // ... outros campos ...
  appearance: {
    brandColor: '#2563EB',
    logoUrl: 'https://yourapp.com/logo.png',
    companyName: 'Acme Tecnologia Ltda',
    title: 'Assine seu contrato',
    submitLabel: 'Concordo e assino',
    headerStyle: 'full',
  },
});
Campo Tipo Descrição
brandColor string Cor primária em hex (botões, links)
logoUrl string URL do logotipo (recomendado: 200x60px)
companyName string Nome exibido no cabeçalho
title string Título da página de assinatura
submitLabel string Texto do botão de confirmação
headerStyle 'full' | 'minimal' Estilo do cabeçalho

9. Próximos passos

Escolha o guia detalhado do perfil que melhor atende ao seu caso de uso:

Ou vá direto para as Receitas Copy-Paste com código pronto para copiar e executar.