Guias de Desenvolvimento

Política e Conformidade

Signing Sessions

Guias dos SDKs

Envelopes (Multi-Signatario)

Procurando scripts prontos para copiar e colar? Veja as Receitas de Envelopes.

Envelopes estendem as Signing Sessions para fluxos com multiplos signatarios sobre um unico documento. Crie um envelope, adicione sessoes (uma por signatario) e acompanhe a conclusao via webhook ou polling. Ao final, gere um carimbo combinado com todas as assinaturas.

Formatos suportados: Envelopes aceitam documentos em diversos formatos além de PDF (DOCX, XLSX, imagens, etc.). Para documentos não-PDF, o campo filename (ou document_filename) é obrigatório. Documentos PDF utilizam PAdES; demais formatos utilizam CAdES (.p7s).


Comparacao: Signing Sessions vs Envelopes

Aspecto Signing Sessions Envelopes
Signatarios 1 N (2+)
Documento Por sessao Compartilhado
Modo - PARALLEL / SEQUENTIAL
URL de checkout 1 URL 1 URL por signatario
Carimbo Individual Combinado com todas assinaturas

Passo 1: Criar envelope

Cria o envelope com modo PARALLEL, 2 signatarios e documento em base64. Retorna envelopeId, status e documentHash. O documento pode ser PDF ou outro formato suportado.

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

const envelope = await client.envelopes.create({
  signingMode: 'PARALLEL',
  totalSigners: 2,
  document: { content: documentBase64, filename: 'contrato.pdf' },
  returnUrl: 'https://app.example.com/done',
  locale: 'pt-BR',
  expiresInMinutes: 1440,
});
console.log(envelope.envelopeId, envelope.status);
from signdocs_brasil import SignDocsBrasilClient, ClientConfig
from signdocs_brasil.models import CreateEnvelopeRequest

envelope = client.envelopes.create(CreateEnvelopeRequest(
    signing_mode='PARALLEL',
    total_signers=2,
    document_content=document_base64,
    document_filename='contrato.pdf',
    return_url='https://app.example.com/done',
    locale='pt-BR',
    expires_in_minutes=1440,
))
print(envelope.envelope_id, envelope.status)
envelope, err := client.Envelopes.Create(ctx, &signdocs.CreateEnvelopeRequest{
    SigningMode:       signdocs.SigningModeParallel,
    TotalSigners:     2,
    DocumentContent:  documentBase64,
    DocumentFilename: "contrato.pdf",
    ReturnURL:        "https://app.example.com/done",
    Locale:           "pt-BR",
    ExpiresInMinutes: 1440,
})
if err != nil { log.Fatal(err) }
fmt.Println(envelope.EnvelopeID, envelope.Status)
CreateEnvelopeRequest request = new CreateEnvelopeRequest();
request.signingMode = "PARALLEL";
request.totalSigners = 2;
request.document = new CreateEnvelopeRequest.Document(documentBase64, "contrato.pdf");
request.returnUrl = "https://app.example.com/done";
request.locale = "pt-BR";
request.expiresInMinutes = 1440;

Envelope envelope = client.envelopes().create(request);
System.out.println(envelope.envelopeId + " " + envelope.status);
use SignDocsBrasil\Api\Models\CreateEnvelopeRequest;

$envelope = $client->envelopes->create(new CreateEnvelopeRequest(
    signingMode: 'PARALLEL',
    totalSigners: 2,
    documentContent: $documentBase64,
    documentFilename: 'contrato.pdf',
    returnUrl: 'https://app.example.com/done',
    locale: 'pt-BR',
    expiresInMinutes: 1440,
));
echo $envelope->envelopeId . ' ' . $envelope->status;
using SignDocsBrasil.Api;
using SignDocsBrasil.Api.Models;

var envelope = await client.Envelopes.CreateAsync(new CreateEnvelopeRequest
{
    SigningMode = "PARALLEL",
    TotalSigners = 2,
    Document = new InlineDocument { Content = documentBase64, Filename = "contrato.pdf" },
    ReturnUrl = "https://app.example.com/done",
    Locale = "pt-BR",
    ExpiresInMinutes = 1440,
});
Console.WriteLine($"Envelope ID: {envelope.EnvelopeId} Status: {envelope.Status}");

Passo 2: Adicionar signatarios

Adicione uma sessao por signatario. Cada chamada retorna sessionId, url e clientSecret para aquele signatario.

const session1 = await client.envelopes.addSession(envelope.envelopeId, {
  signer: { name: 'Joao Silva', email: 'joao@example.com', userExternalId: 'user-001' },
  policy: { profile: 'CLICK_ONLY' },
  purpose: 'DOCUMENT_SIGNATURE',
  signerIndex: 1,
});

const session2 = await client.envelopes.addSession(envelope.envelopeId, {
  signer: { name: 'Maria Souza', email: 'maria@example.com', userExternalId: 'user-002' },
  policy: { profile: 'CLICK_ONLY' },
  purpose: 'DOCUMENT_SIGNATURE',
  signerIndex: 2,
});
console.log(session1.url, session2.url);
from signdocs_brasil.models import AddEnvelopeSessionRequest

session1 = client.envelopes.add_session(envelope.envelope_id, AddEnvelopeSessionRequest(
    signer_name='Joao Silva',
    signer_email='joao@example.com',
    signer_user_external_id='user-001',
    policy_profile='CLICK_ONLY',
    purpose='DOCUMENT_SIGNATURE',
    signer_index=1,
))

session2 = client.envelopes.add_session(envelope.envelope_id, AddEnvelopeSessionRequest(
    signer_name='Maria Souza',
    signer_email='maria@example.com',
    signer_user_external_id='user-002',
    policy_profile='CLICK_ONLY',
    purpose='DOCUMENT_SIGNATURE',
    signer_index=2,
))
print(session1.url, session2.url)
session1, err := client.Envelopes.AddSession(ctx, envelope.EnvelopeID, &signdocs.AddEnvelopeSessionRequest{
    SignerName:           "Joao Silva",
    SignerEmail:          "joao@example.com",
    SignerUserExternalID: "user-001",
    PolicyProfile:       signdocs.PolicyProfileClickOnly,
    Purpose:             signdocs.PurposeDocumentSignature,
    SignerIndex:          1,
})
if err != nil { log.Fatal(err) }

session2, err := client.Envelopes.AddSession(ctx, envelope.EnvelopeID, &signdocs.AddEnvelopeSessionRequest{
    SignerName:           "Maria Souza",
    SignerEmail:          "maria@example.com",
    SignerUserExternalID: "user-002",
    PolicyProfile:       signdocs.PolicyProfileClickOnly,
    Purpose:             signdocs.PurposeDocumentSignature,
    SignerIndex:          2,
})
if err != nil { log.Fatal(err) }
fmt.Println(session1.URL, session2.URL)
AddEnvelopeSessionRequest req1 = new AddEnvelopeSessionRequest();
req1.signer = new AddEnvelopeSessionRequest.Signer("Joao Silva", "joao@example.com", "user-001");
req1.policy = new Policy("CLICK_ONLY");
req1.purpose = "DOCUMENT_SIGNATURE";
req1.signerIndex = 1;
EnvelopeSession session1 = client.envelopes().addSession(envelope.envelopeId, req1);

AddEnvelopeSessionRequest req2 = new AddEnvelopeSessionRequest();
req2.signer = new AddEnvelopeSessionRequest.Signer("Maria Souza", "maria@example.com", "user-002");
req2.policy = new Policy("CLICK_ONLY");
req2.purpose = "DOCUMENT_SIGNATURE";
req2.signerIndex = 2;
EnvelopeSession session2 = client.envelopes().addSession(envelope.envelopeId, req2);

System.out.println(session1.url + " " + session2.url);
use SignDocsBrasil\Api\Models\AddEnvelopeSessionRequest;

$session1 = $client->envelopes->addSession($envelope->envelopeId, new AddEnvelopeSessionRequest(
    signerName: 'Joao Silva',
    signerEmail: 'joao@example.com',
    signerUserExternalId: 'user-001',
    policyProfile: 'CLICK_ONLY',
    purpose: 'DOCUMENT_SIGNATURE',
    signerIndex: 1,
));

$session2 = $client->envelopes->addSession($envelope->envelopeId, new AddEnvelopeSessionRequest(
    signerName: 'Maria Souza',
    signerEmail: 'maria@example.com',
    signerUserExternalId: 'user-002',
    policyProfile: 'CLICK_ONLY',
    purpose: 'DOCUMENT_SIGNATURE',
    signerIndex: 2,
));
echo $session1->url . ' ' . $session2->url;
var session1 = await client.Envelopes.AddSessionAsync(envelope.EnvelopeId, new AddEnvelopeSessionRequest
{
    Policy = new Policy { Profile = "CLICK_ONLY" },
    Signer = new Signer { Name = "Joao Silva", Email = "joao@example.com", UserExternalId = "user-001" },
    Purpose = "DOCUMENT_SIGNATURE",
    SignerIndex = 1,
    Locale = "pt-BR",
});

var session2 = await client.Envelopes.AddSessionAsync(envelope.EnvelopeId, new AddEnvelopeSessionRequest
{
    Policy = new Policy { Profile = "CLICK_ONLY" },
    Signer = new Signer { Name = "Maria Souza", Email = "maria@example.com", UserExternalId = "user-002" },
    Purpose = "DOCUMENT_SIGNATURE",
    SignerIndex = 2,
    Locale = "pt-BR",
});
Console.WriteLine($"Session 1 Client Secret: {session1.ClientSecret}");
Console.WriteLine($"Session 2 Client Secret: {session2.ClientSecret}");

Passo 3: Redirecionar signatarios

Cada signatario recebe sua propria URL de checkout. Use redirecionamento ou o JS SDK embutido.

<!-- Signatario 1 -->
<a href="{{ session1.url }}">Joao — Assinar documento</a>

<!-- Signatario 2 -->
<a href="{{ session2.url }}">Maria — Assinar documento</a>

<!-- Checkout embutido (exemplo para signatario 1) -->
<div id="signdocs-checkout"></div>
<script src="https://cdn.signdocs.com.br/v1/signdocs-brasil.js"></script>
<script>
  const sd = SignDocsBrasil.init({ locale: 'pt-BR' });
  sd.checkout({
    clientSecret: '{{ session1.clientSecret }}',
    onComplete: function (event) { window.location.href = '/done'; },
    onError: function (event) { console.error(event.code); },
    onClose: function () { console.log('Cancelado'); },
  });
</script>

Passo 4: Acompanhar conclusao

Webhook

Receba SIGNING_SESSION.COMPLETED para cada signatario individual, e ENVELOPE.COMPLETED quando todos terminarem.

{
  "event": "SIGNING_SESSION.COMPLETED",
  "payload": {
    "sessionId": "ss_abc123", "transactionId": "tx_def456",
    "envelopeId": "env_xyz789",
    "signerIndex": 1, "signerName": "Joao Silva",
    "status": "COMPLETED", "evidenceId": "ev_ghi789",
    "completedAt": "2026-03-27T10:15:00Z"
  }
}
{
  "event": "ENVELOPE.COMPLETED",
  "payload": {
    "envelopeId": "env_xyz789",
    "status": "COMPLETED",
    "totalSigners": 2, "completedSessions": 2,
    "completedAt": "2026-03-27T10:20:00Z"
  }
}

Polling

Consulte GET /v1/envelopes/{id} para verificar o status e as sessoes.

const detail = await client.envelopes.get(envelope.envelopeId);
console.log(detail.status, detail.completedSessions + '/' + detail.totalSigners);
for (const s of detail.sessions) {
  console.log(s.signerName, s.status, s.evidenceId);
}
detail = client.envelopes.get(envelope.envelope_id)
print(detail.status, f"{detail.completed_sessions}/{detail.total_signers}")
for s in detail.sessions:
    print(s.signer_name, s.status, s.evidence_id)
detail, err := client.Envelopes.Get(ctx, envelope.EnvelopeID)
if err != nil { log.Fatal(err) }
fmt.Printf("%s %d/%d\n", detail.Status, detail.CompletedSessions, detail.TotalSigners)
for _, s := range detail.Sessions {
    fmt.Println(s.SignerName, s.Status, s.EvidenceID)
}
EnvelopeDetail detail = client.envelopes().get(envelope.envelopeId);
System.out.println(detail.status + " " + detail.completedSessions + "/" + detail.totalSigners);
for (EnvelopeSessionSummary s : detail.sessions) {
    System.out.println(s.signerName + " " + s.status + " " + s.evidenceId);
}
$detail = $client->envelopes->get($envelope->envelopeId);
echo $detail->status . ' ' . $detail->completedSessions . '/' . $detail->totalSigners . "\n";
foreach ($detail->sessions as $s) {
    echo $s->signerName . ' ' . $s->status . ' ' . $s->evidenceId . "\n";
}
var detail = await client.Envelopes.GetAsync(envelope.EnvelopeId);
Console.WriteLine($"Status: {detail.Status} {detail.CompletedSessions}/{detail.TotalSigners}");
foreach (var s in detail.Sessions)
    Console.WriteLine($"  {s.SignerName}: {s.Status} (evidence: {s.EvidenceId})");

Passo 5: Carimbo combinado

Apos todas as sessoes concluidas, gere o documento com carimbo combinado contendo todas as assinaturas.

const stamp = await client.envelopes.combinedStamp(envelope.envelopeId);
console.log(stamp.downloadUrl, stamp.signerCount);
stamp = client.envelopes.combined_stamp(envelope.envelope_id)
print(stamp.download_url, stamp.signer_count)
stamp, err := client.Envelopes.CombinedStamp(ctx, envelope.EnvelopeID)
if err != nil { log.Fatal(err) }
fmt.Println(stamp.DownloadURL, stamp.SignerCount)
CombinedStampResponse stamp = client.envelopes().combinedStamp(envelope.envelopeId);
System.out.println(stamp.downloadUrl + " " + stamp.signerCount);
$stamp = $client->envelopes->combinedStamp($envelope->envelopeId);
echo $stamp->downloadUrl . ' ' . $stamp->signerCount;
var stamp = await client.Envelopes.CombinedStampAsync(envelope.EnvelopeId);
Console.WriteLine($"Download URL: {stamp.DownloadUrl}");
Console.WriteLine($"Signer Count: {stamp.SignerCount}");

Modo SEQUENTIAL

Quando signingMode e SEQUENTIAL, as sessoes sao processadas na ordem definida por signerIndex. Cada signatario so pode assinar apos o anterior concluir. A URL de checkout do proximo signatario so se torna ativa apos a conclusao da sessao anterior.

envelope = client.envelopes.create(CreateEnvelopeRequest(
    signing_mode='SEQUENTIAL',
    total_signers=2,
    document_content=document_base64,
    document_filename='contrato.pdf',
))

# signerIndex=1 assina primeiro
session1 = client.envelopes.add_session(envelope.envelope_id, AddEnvelopeSessionRequest(
    signer_name='Joao Silva', signer_email='joao@example.com',
    policy_profile='CLICK_ONLY', signer_index=1,
))

# signerIndex=2 so pode assinar apos session1 concluir
session2 = client.envelopes.add_session(envelope.envelope_id, AddEnvelopeSessionRequest(
    signer_name='Maria Souza', signer_email='maria@example.com',
    policy_profile='CLICK_ONLY', signer_index=2,
))

Resumo do fluxo

envelopes.create ──> envelopeId + status + documentHash
        |
        v
envelopes.add_session (signatario 1) ──> sessionId + url
envelopes.add_session (signatario 2) ──> sessionId + url
        |
        v
  Redirecionar cada signatario para sua URL
        |
        v
  Signatarios completam seus fluxos
        |
   ┌────┴────┐
   v         v
Webhook   Polling (envelopes.get)
   |         |
   └────┬────┘
        v
  ENVELOPE.COMPLETED
        |
        v
  envelopes.combined_stamp ──> downloadUrl