Guias de Desenvolvimento

Signing Sessions

Guias dos SDKs

Envelopes para Assinatura Expressa

Colete assinaturas de múltiplos signatários no mesmo documento com um único upload. Modos paralelo e sequencial.


1. O que são Envelopes

Um Envelope é um container que agrupa múltiplas sessões de assinatura para o mesmo documento. Em vez de criar transações separadas e fazer upload do PDF várias vezes, você:

  1. Cria o envelope com o documento (upload único em base64)
  2. Adiciona cada signatário individualmente — cada um recebe sua própria sessão de assinatura
  3. Acompanha o progresso global do envelope
  4. Gera o PDF combinado com todas as assinaturas ao final

Envelopes são ideais para contratos com múltiplas partes, atas societárias, procurações e qualquer documento que exija mais de uma assinatura.

Pré-requisito: Este guia assume que você já configurou as credenciais e obteve um token de acesso. Veja o Guia de Início Rápido e a Visão Geral da Assinatura Expressa para os fundamentos.


2. Visão geral do fluxo

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

1. Criar envelope                          ──> POST /v1/envelopes
   (documento base64, modo, total)              Upload + criar container
                                           <── { envelopeId, status: CREATED }

2. Adicionar signatário 1                  ──> POST /v1/envelopes/{id}/sessions
   (nome, cpf, perfil, signerIndex: 1)          Cria transação + sessão
                                           <── { sessionId, url, clientSecret }
                                                status: CREATED → ACTIVE

3. Adicionar signatário 2                  ──> POST /v1/envelopes/{id}/sessions
   (nome, cpf, perfil, signerIndex: 2)          Cria transação + sessão
                                           <── { sessionId, url, clientSecret }

   ... (repetir para cada signatário)

4. Consultar progresso                     ──> GET /v1/envelopes/{id}
                                           <── { status, addedSessions, completedSessions, sessions[] }

5. Todos assinaram → COMPLETED             ──> Webhook: ENVELOPE.ALL_SIGNED

6. Gerar PDF combinado                    ──> POST /v1/envelopes/{id}/combined-stamp
                                           <── { downloadUrl, signerCount }

Endpoints

Etapa Endpoint Método HTTP
Criar envelope /v1/envelopes POST 201
Adicionar signatário /v1/envelopes/{id}/sessions POST 201
Consultar envelope /v1/envelopes/{id} GET 200
PDF combinado /v1/envelopes/{id}/combined-stamp POST 200

3. Modos de assinatura

O campo signingMode define como os signatários interagem com o envelope:

PARALLEL SEQUENTIAL
Ordem Todos assinam simultaneamente Ordem rigorosa por signerIndex
Velocidade Mais rápido Depende de cada signatário
Caso de uso Co-signatários, testemunhas, sócios Aprovações hierárquicas, fluxos regulatórios
Notificação Todos recebem ao mesmo tempo Próximo notificado após anterior concluir

4. Criar envelope (POST /v1/envelopes)

O primeiro passo é criar o envelope com o documento em base64. O documento é armazenado uma única vez e reutilizado por todas as sessões de assinatura.

Campos da requisição

Campo Tipo Obrigatório Descrição
signingMode string Sim "PARALLEL" ou "SEQUENTIAL"
totalSigners number Sim Número total de signatários esperados
document.content string Sim Conteúdo base64 do PDF (máx. 10 MB)
document.filename string Não Nome do arquivo
appearance.brandColor string Não Cor da marca em hex (ex: #1a5276)
appearance.logoUrl string Não URL HTTPS do logo para a página de assinatura
locale string Não "pt-BR" (padrão), "en", "es"
returnUrl string Não URL de redirecionamento após assinatura
cancelUrl string Não URL de redirecionamento após cancelamento
expiresInMinutes number Não 5 a 10.080 min (padrão: 1.440 = 24h)
metadata object Não Dados customizados (chave ≤ 256, valor ≤ 1024 chars)

Exemplo

import { readFileSync } from 'fs';

const pdfBase64 = readFileSync('contrato.pdf').toString('base64');

const envelope = await client.envelopes.create({
  signingMode: 'SEQUENTIAL',
  totalSigners: 2,
  document: {
    content: pdfBase64,
    filename: 'contrato-prestacao-servicos.pdf',
  },
  returnUrl: 'https://app.empresa.com.br/assinatura/sucesso',
  cancelUrl: 'https://app.empresa.com.br/assinatura/cancelado',
  expiresInMinutes: 4320, // 3 dias
  metadata: { contractId: 'CTR-2026-001' },
});

console.log(envelope.envelopeId); // "env_01J..."
console.log(envelope.status);     // "CREATED"
import base64
from pathlib import Path
from signdocs_brasil.models.envelope import CreateEnvelopeRequest

pdf_base64 = base64.b64encode(Path('contrato.pdf').read_bytes()).decode()

envelope = client.envelopes.create(CreateEnvelopeRequest(
    signing_mode='SEQUENTIAL',
    total_signers=2,
    document_content=pdf_base64,
    document_filename='contrato-prestacao-servicos.pdf',
    return_url='https://app.empresa.com.br/assinatura/sucesso',
    cancel_url='https://app.empresa.com.br/assinatura/cancelado',
    expires_in_minutes=4320,  # 3 dias
    metadata={'contractId': 'CTR-2026-001'},
))

print(envelope.envelope_id)  # "env_01J..."
print(envelope.status)       # "CREATED"
pdfBytes, _ := os.ReadFile("contrato.pdf")
pdfBase64 := base64.StdEncoding.EncodeToString(pdfBytes)

envelope, err := client.Envelopes.Create(ctx, &signdocs.CreateEnvelopeRequest{
    SigningMode:  "SEQUENTIAL",
    TotalSigners: 2,
    Document: signdocs.EnvelopeDocument{
        Content:  pdfBase64,
        Filename: "contrato-prestacao-servicos.pdf",
    },
    ReturnURL:        "https://app.empresa.com.br/assinatura/sucesso",
    CancelURL:        "https://app.empresa.com.br/assinatura/cancelado",
    ExpiresInMinutes: 4320,
    Metadata:         map[string]string{"contractId": "CTR-2026-001"},
})
if err != nil { log.Fatal(err) }

fmt.Println(envelope.EnvelopeID) // "env_01J..."
fmt.Println(envelope.Status)     // "CREATED"
byte[] pdfBytes = Files.readAllBytes(Path.of("contrato.pdf"));
String pdfBase64 = Base64.getEncoder().encodeToString(pdfBytes);

Envelope envelope = client.envelopes().create(CreateEnvelopeRequest.builder()
    .signingMode("SEQUENTIAL")
    .totalSigners(2)
    .documentContent(pdfBase64)
    .documentFilename("contrato-prestacao-servicos.pdf")
    .returnUrl("https://app.empresa.com.br/assinatura/sucesso")
    .cancelUrl("https://app.empresa.com.br/assinatura/cancelado")
    .expiresInMinutes(4320)
    .metadata(Map.of("contractId", "CTR-2026-001"))
    .build());

System.out.println(envelope.getEnvelopeId()); // "env_01J..."
System.out.println(envelope.getStatus());     // "CREATED"
$pdfBase64 = base64_encode(file_get_contents('contrato.pdf'));

$envelope = $client->envelopes()->create([
    'signingMode' => 'SEQUENTIAL',
    'totalSigners' => 2,
    'document' => [
        'content' => $pdfBase64,
        'filename' => 'contrato-prestacao-servicos.pdf',
    ],
    'returnUrl' => 'https://app.empresa.com.br/assinatura/sucesso',
    'cancelUrl' => 'https://app.empresa.com.br/assinatura/cancelado',
    'expiresInMinutes' => 4320,
    'metadata' => ['contractId' => 'CTR-2026-001'],
]);

echo $envelope->envelopeId; // "env_01J..."
echo $envelope->status;     // "CREATED"
var pdfBase64 = Convert.ToBase64String(File.ReadAllBytes("contrato.pdf"));

var envelope = await client.Envelopes.CreateAsync(new CreateEnvelopeRequest
{
    SigningMode = "SEQUENTIAL",
    TotalSigners = 2,
    Document = new EnvelopeDocument
    {
        Content = pdfBase64,
        Filename = "contrato-prestacao-servicos.pdf",
    },
    ReturnUrl = "https://app.empresa.com.br/assinatura/sucesso",
    CancelUrl = "https://app.empresa.com.br/assinatura/cancelado",
    ExpiresInMinutes = 4320,
    Metadata = new Dictionary<string, string> { ["contractId"] = "CTR-2026-001" },
});

Console.WriteLine(envelope.EnvelopeId); // "env_01J..."
Console.WriteLine(envelope.Status);     // "CREATED"

Resposta (201 Created)

{
  "envelopeId": "env_01J5X7K9M2N8P4Q6R3S1T0",
  "status": "CREATED",
  "signingMode": "SEQUENTIAL",
  "totalSigners": 2,
  "documentHash": "sha256:a1b2c3d4e5f6...",
  "createdAt": "2026-03-20T10:00:00.000Z",
  "expiresAt": "2026-03-23T10:00:00.000Z"
}

5. Adicionar signatários (POST /v1/envelopes/{id}/sessions)

Cada signatário é adicionado individualmente. A API cria atomicamente uma transação, as etapas de verificação e um token de embed — tudo em uma única chamada.

Campos da requisição

Campo Tipo Obrigatório Descrição
signer.userExternalId string Sim ID externo do usuário no seu sistema
signer.name string Sim Nome completo do signatário
signer.cpf string Depende CPF (obrigatório para biometria)
signer.email string Depende E-mail (obrigatório para OTP via e-mail)
signer.phone string Depende Telefone (obrigatório para OTP via SMS)
signer.otpChannel string Não "SMS" ou "EMAIL"
signer.birthDate string Depende Data de nascimento (SERPRO)
policy.profile string Sim Perfil de assinatura (ver tabela abaixo)
signerIndex number Sim Ordem do signatário (1-based)
purpose string Não Finalidade (padrão: "DOCUMENT_SIGNATURE")
returnUrl string Não Sobrescreve o returnUrl do envelope
cancelUrl string Não Sobrescreve o cancelUrl do envelope
metadata object Não Metadados por sessão

Perfis de assinatura disponíveis

Perfil Descrição Campos obrigatórios
CLICK_ONLY Aceite simples por clique name, cpf ou cnpj
CLICK_PLUS_OTP Aceite + código OTP email (ou phone)
BIOMETRIC Verificação facial cpf
BIOMETRIC_PLUS_OTP Biometria + OTP cpf + email
DIGITAL_CERTIFICATE Certificado ICP-Brasil A1 cpf
BIOMETRIC_SERPRO Biometria contra base SERPRO cpf + birthDate
BIOMETRIC_SERPRO_AUTO_FALLBACK NT65 consignado cpf + birthDate
CUSTOM Etapas customizadas varia

Restrição: O perfil DIGITAL_CERTIFICATE não pode ser combinado com outros perfis no mesmo envelope. Se um signatário usar certificado digital, todos devem usar.

Exemplo — adicionar signatários

// Signatário 1 — Diretor Financeiro (biometria + OTP)
const session1 = await client.envelopes.addSession(envelope.envelopeId, {
  signer: {
    userExternalId: 'usr_maria_001',
    name: 'Maria Souza',
    cpf: '123.456.789-00',
    email: 'maria.souza@empresa.com.br',
    phone: '+5511999990001',
    otpChannel: 'SMS',
  },
  policy: { profile: 'BIOMETRIC_PLUS_OTP' },
  signerIndex: 1,
  metadata: { role: 'CFO' },
});

console.log(session1.url);          // URL da página de assinatura
console.log(session1.clientSecret); // Para integração via SDK frontend

// Signatário 2 — Diretor Jurídico (aceite simples)
const session2 = await client.envelopes.addSession(envelope.envelopeId, {
  signer: {
    userExternalId: 'usr_carlos_002',
    name: 'Carlos Lima',
    cpf: '987.654.321-00',
  },
  policy: { profile: 'CLICK_ONLY' },
  signerIndex: 2,
});
from signdocs_brasil.models.envelope import AddEnvelopeSessionRequest

# Signatário 1 — Diretor Financeiro (biometria + OTP)
session1 = client.envelopes.add_session(envelope.envelope_id, AddEnvelopeSessionRequest(
    signer_name='Maria Souza',
    signer_cpf='123.456.789-00',
    signer_email='maria.souza@empresa.com.br',
    signer_phone='+5511999990001',
    signer_otp_channel='SMS',
    signer_user_external_id='usr_maria_001',
    policy_profile='BIOMETRIC_PLUS_OTP',
    signer_index=1,
    metadata={'role': 'CFO'},
))

print(session1.url)            # URL da página de assinatura
print(session1.client_secret)  # Para integração via SDK frontend

# Signatário 2 — Diretor Jurídico (aceite simples)
session2 = client.envelopes.add_session(envelope.envelope_id, AddEnvelopeSessionRequest(
    signer_name='Carlos Lima',
    signer_cpf='987.654.321-00',
    signer_user_external_id='usr_carlos_002',
    policy_profile='CLICK_ONLY',
    signer_index=2,
))
// Signatário 1 — Diretor Financeiro (biometria + OTP)
session1, err := client.Envelopes.AddSession(ctx, envelope.EnvelopeID, &signdocs.AddEnvelopeSessionRequest{
    Signer: signdocs.Signer{
        UserExternalID: "usr_maria_001",
        Name:           "Maria Souza",
        CPF:            "123.456.789-00",
        Email:          "maria.souza@empresa.com.br",
        Phone:          "+5511999990001",
        OTPChannel:     "SMS",
    },
    Policy:      signdocs.Policy{Profile: "BIOMETRIC_PLUS_OTP"},
    SignerIndex:  1,
    Metadata:    map[string]string{"role": "CFO"},
})
if err != nil { log.Fatal(err) }

fmt.Println(session1.URL)          // URL da página de assinatura
fmt.Println(session1.ClientSecret) // Para integração via SDK frontend

// Signatário 2 — Diretor Jurídico (aceite simples)
session2, err := client.Envelopes.AddSession(ctx, envelope.EnvelopeID, &signdocs.AddEnvelopeSessionRequest{
    Signer: signdocs.Signer{
        UserExternalID: "usr_carlos_002",
        Name:           "Carlos Lima",
        CPF:            "987.654.321-00",
    },
    Policy:     signdocs.Policy{Profile: "CLICK_ONLY"},
    SignerIndex: 2,
})
if err != nil { log.Fatal(err) }
// Signatário 1 — Diretor Financeiro (biometria + OTP)
EnvelopeSession session1 = client.envelopes().addSession(
    envelope.getEnvelopeId(),
    AddEnvelopeSessionRequest.builder()
        .signerName("Maria Souza")
        .signerCpf("123.456.789-00")
        .signerEmail("maria.souza@empresa.com.br")
        .signerPhone("+5511999990001")
        .signerOtpChannel("SMS")
        .signerUserExternalId("usr_maria_001")
        .policyProfile("BIOMETRIC_PLUS_OTP")
        .signerIndex(1)
        .metadata(Map.of("role", "CFO"))
        .build());

System.out.println(session1.getUrl());          // URL da página de assinatura
System.out.println(session1.getClientSecret()); // Para integração via SDK frontend

// Signatário 2 — Diretor Jurídico (aceite simples)
EnvelopeSession session2 = client.envelopes().addSession(
    envelope.getEnvelopeId(),
    AddEnvelopeSessionRequest.builder()
        .signerName("Carlos Lima")
        .signerCpf("987.654.321-00")
        .signerUserExternalId("usr_carlos_002")
        .policyProfile("CLICK_ONLY")
        .signerIndex(2)
        .build());
// Signatário 1 — Diretor Financeiro (biometria + OTP)
$session1 = $client->envelopes()->addSession($envelope->envelopeId, [
    'signer' => [
        'userExternalId' => 'usr_maria_001',
        'name' => 'Maria Souza',
        'cpf' => '123.456.789-00',
        'email' => 'maria.souza@empresa.com.br',
        'phone' => '+5511999990001',
        'otpChannel' => 'SMS',
    ],
    'policy' => ['profile' => 'BIOMETRIC_PLUS_OTP'],
    'signerIndex' => 1,
    'metadata' => ['role' => 'CFO'],
]);

echo $session1->url;          // URL da página de assinatura
echo $session1->clientSecret; // Para integração via SDK frontend

// Signatário 2 — Diretor Jurídico (aceite simples)
$session2 = $client->envelopes()->addSession($envelope->envelopeId, [
    'signer' => [
        'userExternalId' => 'usr_carlos_002',
        'name' => 'Carlos Lima',
        'cpf' => '987.654.321-00',
    ],
    'policy' => ['profile' => 'CLICK_ONLY'],
    'signerIndex' => 2,
]);
// Signatário 1 — Diretor Financeiro (biometria + OTP)
var session1 = await client.Envelopes.AddSessionAsync(envelope.EnvelopeId, new AddEnvelopeSessionRequest
{
    Signer = new Signer
    {
        UserExternalId = "usr_maria_001",
        Name = "Maria Souza",
        Cpf = "123.456.789-00",
        Email = "maria.souza@empresa.com.br",
        Phone = "+5511999990001",
        OtpChannel = "SMS",
    },
    Policy = new Policy { Profile = "BIOMETRIC_PLUS_OTP" },
    SignerIndex = 1,
    Metadata = new Dictionary<string, string> { ["role"] = "CFO" },
});

Console.WriteLine(session1.Url);          // URL da página de assinatura
Console.WriteLine(session1.ClientSecret); // Para integração via SDK frontend

// Signatário 2 — Diretor Jurídico (aceite simples)
var session2 = await client.Envelopes.AddSessionAsync(envelope.EnvelopeId, new AddEnvelopeSessionRequest
{
    Signer = new Signer
    {
        UserExternalId = "usr_carlos_002",
        Name = "Carlos Lima",
        Cpf = "987.654.321-00",
    },
    Policy = new Policy { Profile = "CLICK_ONLY" },
    SignerIndex = 2,
});

Resposta (201 Created)

{
  "sessionId": "ses_01J5X8A2B3C4D5E6F7G8H9",
  "transactionId": "txn_01J5X8A2B3C4D5E6F7G8",
  "signerIndex": 1,
  "status": "ACTIVE",
  "url": "https://sign.signdocs.com.br/ses_01J5X8A2B3C4D5E6F7G8H9?token=eyJ...",
  "clientSecret": "cs_live_a1b2c3d4e5f6...",
  "expiresAt": "2026-03-23T10:00:00.000Z"
}

Use a url para redirecionar o signatário ou embutir em iframe. Use o clientSecret com o SDK JavaScript para integração inline:

// No frontend — embutir sessão de assinatura
sd.checkout({ clientSecret: session1.clientSecret });

6. Consultar status (GET /v1/envelopes/{id})

Consulte o envelope a qualquer momento para verificar o progresso global e o status individual de cada signatário.

const detail = await client.envelopes.get(envelope.envelopeId);

console.log(detail.status);            // "ACTIVE"
console.log(detail.completedSessions); // 1
console.log(detail.totalSigners);      // 2

for (const s of detail.sessions) {
  console.log(`${s.signerName} (${s.signerIndex}): ${s.status}`);
}
detail = client.envelopes.get(envelope.envelope_id)

print(detail.status)              # "ACTIVE"
print(detail.completed_sessions)  # 1
print(detail.total_signers)       # 2

for s in detail.sessions:
    print(f"{s.signer_name} ({s.signer_index}): {s.status}")
detail, err := client.Envelopes.Get(ctx, envelope.EnvelopeID)
if err != nil { log.Fatal(err) }

fmt.Println(detail.Status)            // "ACTIVE"
fmt.Println(detail.CompletedSessions) // 1
fmt.Println(detail.TotalSigners)      // 2

for _, s := range detail.Sessions {
    fmt.Printf("%s (%d): %s\n", s.SignerName, s.SignerIndex, s.Status)
}
EnvelopeDetail detail = client.envelopes().get(envelope.getEnvelopeId());

System.out.println(detail.getStatus());            // "ACTIVE"
System.out.println(detail.getCompletedSessions()); // 1
System.out.println(detail.getTotalSigners());       // 2

for (EnvelopeSessionSummary s : detail.getSessions()) {
    System.out.printf("%s (%d): %s%n", s.getSignerName(), s.getSignerIndex(), s.getStatus());
}
$detail = $client->envelopes()->get($envelope->envelopeId);

echo $detail->status;            // "ACTIVE"
echo $detail->completedSessions; // 1
echo $detail->totalSigners;      // 2

foreach ($detail->sessions as $s) {
    echo "{$s->signerName} ({$s->signerIndex}): {$s->status}\n";
}
var detail = await client.Envelopes.GetAsync(envelope.EnvelopeId);

Console.WriteLine(detail.Status);            // "ACTIVE"
Console.WriteLine(detail.CompletedSessions); // 1
Console.WriteLine(detail.TotalSigners);      // 2

foreach (var s in detail.Sessions)
{
    Console.WriteLine($"{s.SignerName} ({s.SignerIndex}): {s.Status}");
}

Resposta (200 OK)

{
  "envelopeId": "env_01J5X7K9M2N8P4Q6R3S1T0",
  "status": "ACTIVE",
  "signingMode": "SEQUENTIAL",
  "totalSigners": 2,
  "addedSessions": 2,
  "completedSessions": 1,
  "documentHash": "sha256:a1b2c3d4e5f6...",
  "sessions": [
    {
      "sessionId": "ses_01J5X8A2B3C4D5E6F7G8H9",
      "transactionId": "txn_01J5X8A2B3C4D5E6F7G8",
      "signerIndex": 1,
      "signerName": "Maria Souza",
      "status": "COMPLETED",
      "completedAt": "2026-03-20T14:30:00.000Z",
      "evidenceId": "evd_01J5X9B3C4D5E6F7G8H9"
    },
    {
      "sessionId": "ses_01J5X8C4D5E6F7G8H9I0J1",
      "transactionId": "txn_01J5X8C4D5E6F7G8H9I0",
      "signerIndex": 2,
      "signerName": "Carlos Lima",
      "status": "ACTIVE"
    }
  ],
  "createdAt": "2026-03-20T10:00:00.000Z",
  "updatedAt": "2026-03-20T14:30:00.000Z",
  "expiresAt": "2026-03-23T10:00:00.000Z"
}

7. Gerar PDF combinado (POST /v1/envelopes/{id}/combined-stamp)

Quando todos os signatários concluírem (status COMPLETED), gere o PDF final com todas as assinaturas combinadas:

const combined = await client.envelopes.combinedStamp(envelope.envelopeId);

console.log(combined.downloadUrl); // URL temporária para download
console.log(combined.signerCount); // 2

// Baixar o PDF combinado
const res = await fetch(combined.downloadUrl);
const pdf = Buffer.from(await res.arrayBuffer());
writeFileSync('contrato-assinado-completo.pdf', pdf);
import httpx

combined = client.envelopes.combined_stamp(envelope.envelope_id)

print(combined.download_url)  # URL temporária para download
print(combined.signer_count)  # 2

# Baixar o PDF combinado
pdf = httpx.get(combined.download_url).content
Path('contrato-assinado-completo.pdf').write_bytes(pdf)
combined, err := client.Envelopes.CombinedStamp(ctx, envelope.EnvelopeID)
if err != nil { log.Fatal(err) }

fmt.Println(combined.DownloadURL) // URL temporária para download
fmt.Println(combined.SignerCount) // 2

// Baixar o PDF combinado
resp, _ := http.Get(combined.DownloadURL)
defer resp.Body.Close()
pdf, _ := io.ReadAll(resp.Body)
os.WriteFile("contrato-assinado-completo.pdf", pdf, 0644)
CombinedStampResponse combined = client.envelopes().combinedStamp(envelope.getEnvelopeId());

System.out.println(combined.getDownloadUrl()); // URL temporária para download
System.out.println(combined.getSignerCount()); // 2

// Baixar o PDF combinado
byte[] pdf = new URL(combined.getDownloadUrl()).openStream().readAllBytes();
Files.write(Path.of("contrato-assinado-completo.pdf"), pdf);
$combined = $client->envelopes()->combinedStamp($envelope->envelopeId);

echo $combined->downloadUrl; // URL temporária para download
echo $combined->signerCount; // 2

// Baixar o PDF combinado
$pdf = file_get_contents($combined->downloadUrl);
file_put_contents('contrato-assinado-completo.pdf', $pdf);
var combined = await client.Envelopes.CombinedStampAsync(envelope.EnvelopeId);

Console.WriteLine(combined.DownloadUrl); // URL temporária para download
Console.WriteLine(combined.SignerCount); // 2

// Baixar o PDF combinado
var pdf = await new HttpClient().GetByteArrayAsync(combined.DownloadUrl);
File.WriteAllBytes("contrato-assinado-completo.pdf", pdf);

Resposta (200 OK)

{
  "envelopeId": "env_01J5X7K9M2N8P4Q6R3S1T0",
  "downloadUrl": "https://storage.signdocs.com.br/signed/env_01J5X7K9M2N8P4Q6R3S1T0.pdf?token=...",
  "expiresIn": 3600,
  "signerCount": 2
}

Importante: A URL de download expira (padrão: 1 hora). Armazene o PDF no seu próprio storage para acesso permanente.


8. Ciclo de vida do envelope

O envelope transiciona automaticamente entre estados:

CREATED ──(primeira sessão adicionada)──> ACTIVE ──(todos assinaram)──> COMPLETED

                                          ACTIVE ──(cancelado via API)──> CANCELLED
                                          ACTIVE ──(prazo expirou)──> EXPIRED
Status Descrição Ações permitidas
CREATED Envelope criado, sem sessões Adicionar sessões
ACTIVE Pelo menos uma sessão adicionada Adicionar sessões, consultar, cancelar
COMPLETED Todos os signatários assinaram Consultar, gerar PDF combinado
CANCELLED Cancelado Consultar
EXPIRED Prazo expirou Consultar

9. Webhooks

O SignDocs emite eventos específicos durante o ciclo de vida do envelope. Configure webhooks para reagir em tempo real. Veja o Guia de Webhooks para configuração completa.

Evento Quando
ENVELOPE.CREATED Envelope criado com sucesso
ENVELOPE.ALL_SIGNED Último signatário concluiu — envelope COMPLETED
TRANSACTION.CREATED Sessão adicionada ao envelope (por signatário)
SIGNING_SESSION.CREATED Sessão de assinatura criada
SIGNING_SESSION.COMPLETED Signatário concluiu sua assinatura
SIGNING_SESSION.EXPIRED Sessão do signatário expirou

Exemplo — reagir ao evento ENVELOPE.ALL_SIGNED

// Webhook handler — gerar PDF combinado automaticamente
app.post('/webhooks/signdocs', async (req, res) => {
  const event = req.body;

  if (event.type === 'ENVELOPE.ALL_SIGNED') {
    const envelopeId = event.data.envelopeId;

    // Gerar PDF combinado
    const combined = await client.envelopes.combinedStamp(envelopeId);

    // Baixar e armazenar
    const pdf = await fetch(combined.downloadUrl).then(r => r.arrayBuffer());
    await saveToStorage(`envelopes/${envelopeId}/signed.pdf`, Buffer.from(pdf));

    console.log(`Envelope ${envelopeId} completo — ${combined.signerCount} assinaturas`);
  }

  res.status(200).json({ received: true });
});

10. Limites e restrições

Limite Valor Observação
Tamanho máximo do documento 10 MB Base64 aumenta ~33% — arquivo original ≤ 7,5 MB
Expiração mínima 5 minutos
Expiração máxima 10.080 minutos (7 dias)
Expiração padrão 1.440 minutos (24 horas) Se expiresInMinutes não for informado
Escopo OAuth2 transactions:write Para criar envelopes e sessões
Chaves de metadata ≤ 256 caracteres
Valores de metadata ≤ 1.024 caracteres
DIGITAL_CERTIFICATE Exclusivo Não pode ser combinado com outros perfis

11. Próximos passos