Scripts prontos para copiar e colar. Escolha o perfil de assinatura, copie o código, execute.
Referência completa: consulte o Guia de Sessões de Assinatura para detalhes sobre cada campo, personalização e tratamento de erros.
Todas as receitas 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
Tenha um arquivo contrato.pdf no diretório de trabalho. As receitas codificam ele em base64 automaticamente.
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();
Todas as receitas podem ser feitas via HTTP. Obtenha um token primeiro:
ACCESS_TOKEN=$(curl -s -X POST "$SIGNDOCS_BASE_URL/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=$SIGNDOCS_CLIENT_ID&client_secret=$SIGNDOCS_CLIENT_SECRET" \
| jq -r '.access_token')
| HML | Produção | |
|---|---|---|
| Base URL | https://api-hml.signdocs.com.br |
https://api.signdocs.com.br |
| Signing domain | https://sign-hml.signdocs.com.br |
https://sign.signdocs.com.br |
| Dados reais? | Não — sandbox | Sim |
| OTP | sandbox.otpCode retornado na resposta |
Enviado por email/SMS |
O widget JS funciona para todos os perfis de assinatura. Instale uma vez, use em qualquer receita.
npm install @signdocs-brasil/js
<script src="https://cdn.signdocs.com.br/v1/signdocs-brasil.js"></script>
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 declickdo usuário (como acima). Navegadores bloqueiam popups fora de interações diretas.
O fluxo mais básico. O signatário visualiza o documento e aceita com um clique.
Backend: create session ──> clientSecret
│
Frontend: sd.checkout() ──> popup abre
│
Signatário: clica "Aceitar" ──> COMPLETED
│
Backend: poll ou webhook ──> evidenceId
Crie a sessão no backend, passe o clientSecret para o frontend. A página hospedada cuida de tudo.
import { readFileSync } from 'fs';
const pdfBase64 = readFileSync('contrato.pdf').toString('base64');
const session = await client.signingSessions.create({
purpose: 'DOCUMENT_SIGNATURE',
policy: { profile: 'CLICK_ONLY' },
signer: { name: 'João Silva', email: 'joao@example.com', userExternalId: 'user-001' },
document: { content: pdfBase64, filename: 'contrato.pdf' },
returnUrl: 'https://app.example.com/done',
locale: 'pt-BR',
expiresInMinutes: 60,
});
// Envie session.clientSecret para o frontend
console.log('Session ID:', session.sessionId);
console.log('Client Secret:', session.clientSecret);
// Aguarde a conclusão (server-side polling)
const result = await client.signingSessions.waitForCompletion(session.sessionId);
console.log('Status:', result.status); // COMPLETED
console.log('Evidence ID:', result.evidenceId);
import base64
with open('contrato.pdf', 'rb') as f:
pdf_base64 = base64.b64encode(f.read()).decode()
session = client.signing_sessions.create(CreateSigningSessionRequest(
purpose='DOCUMENT_SIGNATURE',
policy=Policy(profile='CLICK_ONLY'),
signer=Signer(name='João Silva', email='joao@example.com', user_external_id='user-001'),
document=InlineDocument(content=pdf_base64, filename='contrato.pdf'),
return_url='https://app.example.com/done',
locale='pt-BR',
expires_in_minutes=60,
))
print('Session ID:', session.session_id)
print('Client Secret:', session.client_secret)
result = client.signing_sessions.wait_for_completion(session.session_id)
print('Status:', result.status) # COMPLETED
print('Evidence ID:', result.evidence_id)
pdfBytes, _ := os.ReadFile("contrato.pdf")
pdfBase64 := base64.StdEncoding.EncodeToString(pdfBytes)
session, err := client.SigningSessions.Create(ctx, &signdocs.CreateSigningSessionRequest{
Purpose: signdocs.PurposeDocumentSignature,
Policy: signdocs.Policy{Profile: signdocs.PolicyProfileClickOnly},
Signer: signdocs.Signer{Name: "João Silva", Email: "joao@example.com", UserExternalID: "user-001"},
Document: &signdocs.DocumentInline{Content: pdfBase64, Filename: "contrato.pdf"},
ReturnURL: "https://app.example.com/done",
Locale: "pt-BR",
ExpiresInMinutes: 60,
})
if err != nil { log.Fatal(err) }
fmt.Println("Session ID:", session.SessionID)
fmt.Println("Client Secret:", session.ClientSecret)
result, err := client.SigningSessions.WaitForCompletion(ctx, session.SessionID)
if err != nil { log.Fatal(err) }
fmt.Println("Status:", result.Status) // COMPLETED
fmt.Println("Evidence ID:", result.EvidenceID)
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
String pdfBase64 = Base64.getEncoder().encodeToString(Files.readAllBytes(Path.of("contrato.pdf")));
CreateSigningSessionRequest request = new CreateSigningSessionRequest();
request.purpose = "DOCUMENT_SIGNATURE";
request.policy = new Policy("CLICK_ONLY");
request.signer = new Signer("João Silva", "joao@example.com", "user-001");
request.document = new CreateSigningSessionRequest.InlineDocument(pdfBase64, "contrato.pdf");
request.returnUrl = "https://app.example.com/done";
request.locale = "pt-BR";
request.expiresInMinutes = 60;
SigningSession session = client.signingSessions().create(request);
System.out.println("Session ID: " + session.sessionId);
System.out.println("Client Secret: " + session.clientSecret);
SigningSessionStatusResponse result = client.signingSessions().waitForCompletion(session.sessionId);
System.out.println("Status: " + result.status); // COMPLETED
System.out.println("Evidence ID: " + result.evidenceId);
$pdfBase64 = base64_encode(file_get_contents('contrato.pdf'));
$session = $client->signingSessions->create(new CreateSigningSessionRequest(
purpose: 'DOCUMENT_SIGNATURE',
policy: new Policy(profile: 'CLICK_ONLY'),
signer: new Signer(name: 'João Silva', email: 'joao@example.com', userExternalId: 'user-001'),
document: ['content' => $pdfBase64, 'filename' => 'contrato.pdf'],
returnUrl: 'https://app.example.com/done',
locale: 'pt-BR',
expiresInMinutes: 60,
));
echo "Session ID: " . $session->sessionId . "\n";
echo "Client Secret: " . $session->clientSecret . "\n";
$result = $client->signingSessions->waitForCompletion($session->sessionId);
echo "Status: " . $result->status . "\n"; // COMPLETED
echo "Evidence ID: " . $result->evidenceId . "\n";
using SignDocsBrasil.Api.Models;
var pdfBase64 = Convert.ToBase64String(await File.ReadAllBytesAsync("contrato.pdf"));
var session = await client.SigningSessions.CreateAsync(new CreateSigningSessionRequest
{
Purpose = "DOCUMENT_SIGNATURE",
Policy = new Policy { Profile = "CLICK_ONLY" },
Signer = new Signer { Name = "João Silva", Email = "joao@example.com", UserExternalId = "user-001" },
Document = new InlineDocument { Content = pdfBase64, Filename = "contrato.pdf" },
ReturnUrl = "https://app.example.com/done",
Locale = "pt-BR",
ExpiresInMinutes = 60,
});
Console.WriteLine($"Session ID: {session.SessionId}");
Console.WriteLine($"Client Secret: {session.ClientSecret}");
var result = await client.SigningSessions.WaitForCompletionAsync(session.SessionId);
Console.WriteLine($"Status: {result.Status}"); // COMPLETED
Console.WriteLine($"Evidence ID: {result.EvidenceId}");
Para integrações server-to-server. Crie a sessão e avance as etapas via API.
const session = await client.signingSessions.create({
purpose: 'DOCUMENT_SIGNATURE',
policy: { profile: 'CLICK_ONLY' },
signer: { name: 'João Silva', email: 'joao@example.com', userExternalId: 'user-001' },
document: { content: pdfBase64, filename: 'contrato.pdf' },
});
// Avançar: aceitar o documento
const result = await fetch(`${process.env.SIGNDOCS_BASE_URL}/v1/signing-sessions/${session.sessionId}/advance`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${session.clientSecret}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'accept' }),
});
const body = await result.json();
console.log('Status:', body.status); // COMPLETED
console.log('Evidence ID:', body.evidenceId);
import requests
session = client.signing_sessions.create(CreateSigningSessionRequest(
purpose='DOCUMENT_SIGNATURE',
policy=Policy(profile='CLICK_ONLY'),
signer=Signer(name='João Silva', email='joao@example.com', user_external_id='user-001'),
document=InlineDocument(content=pdf_base64, filename='contrato.pdf'),
))
result = requests.post(
f"{os.environ['SIGNDOCS_BASE_URL']}/v1/signing-sessions/{session.session_id}/advance",
headers={'Authorization': f'Bearer {session.client_secret}', 'Content-Type': 'application/json'},
json={'action': 'accept'},
)
body = result.json()
print('Status:', body['status']) # COMPLETED
print('Evidence ID:', body['evidenceId'])
session, err := client.SigningSessions.Create(ctx, &signdocs.CreateSigningSessionRequest{
Purpose: signdocs.PurposeDocumentSignature,
Policy: signdocs.Policy{Profile: signdocs.PolicyProfileClickOnly},
Signer: signdocs.Signer{Name: "João Silva", Email: "joao@example.com", UserExternalID: "user-001"},
Document: &signdocs.DocumentInline{Content: pdfBase64, Filename: "contrato.pdf"},
})
if err != nil { log.Fatal(err) }
advanceBody, _ := json.Marshal(map[string]string{"action": "accept"})
req, _ := http.NewRequest("POST",
fmt.Sprintf("%s/v1/signing-sessions/%s/advance", os.Getenv("SIGNDOCS_BASE_URL"), session.SessionID),
bytes.NewReader(advanceBody))
req.Header.Set("Authorization", "Bearer "+session.ClientSecret)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
var body map[string]interface{}
json.NewDecoder(resp.Body).Decode(&body)
fmt.Println("Status:", body["status"]) // COMPLETED
fmt.Println("Evidence ID:", body["evidenceId"])
SigningSession session = client.signingSessions().create(request); // (ver hosted acima)
HttpClient http = HttpClient.newHttpClient();
HttpRequest advanceReq = HttpRequest.newBuilder()
.uri(URI.create(System.getenv("SIGNDOCS_BASE_URL")
+ "/v1/signing-sessions/" + session.sessionId + "/advance"))
.header("Authorization", "Bearer " + session.clientSecret)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"action\":\"accept\"}"))
.build();
HttpResponse<String> resp = http.send(advanceReq, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body()); // {"status":"COMPLETED","evidenceId":"..."}
$session = $client->signingSessions->create(/* ... ver hosted acima ... */);
$ch = curl_init(getenv('SIGNDOCS_BASE_URL')
. '/v1/signing-sessions/' . $session->sessionId . '/advance');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $session->clientSecret,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['action' => 'accept']),
CURLOPT_RETURNTRANSFER => true,
]);
$body = json_decode(curl_exec($ch));
echo "Status: " . $body->status . "\n"; // COMPLETED
echo "Evidence ID: " . $body->evidenceId . "\n";
var session = await client.SigningSessions.CreateAsync(/* ... ver hosted acima ... */);
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", session.ClientSecret);
var advanceRes = await http.PostAsync(
$"{Environment.GetEnvironmentVariable("SIGNDOCS_BASE_URL")}/v1/signing-sessions/{session.SessionId}/advance",
new StringContent("{\"action\":\"accept\"}", System.Text.Encoding.UTF8, "application/json"));
var body = await advanceRes.Content.ReadAsStringAsync();
Console.WriteLine(body); // {"status":"COMPLETED","evidenceId":"..."}
CLICK_ONLY → uma única etapa CLICK_ACCEPT gerada.evidenceId é gerado.O signatário aceita o documento e depois confirma com um código OTP enviado por email ou SMS.
Backend: create session ──> clientSecret
│
Frontend: sd.checkout() ──> popup abre
│
Signatário: clica "Aceitar" ──> OTP enviado
│
Signatário: digita código ──> COMPLETED
│
Backend: poll ou webhook ──> evidenceId
Obrigatório:
signer.email(ousigner.phone+otpChannel: 'sms').
A página hospedada cuida do aceite + input do OTP. Seu backend só cria e espera.
const pdfBase64 = readFileSync('contrato.pdf').toString('base64');
const session = await client.signingSessions.create({
purpose: 'DOCUMENT_SIGNATURE',
policy: { profile: 'CLICK_PLUS_OTP' },
signer: {
name: 'Maria Souza',
email: 'maria@example.com',
userExternalId: 'user-002',
},
document: { content: pdfBase64, filename: 'contrato.pdf' },
returnUrl: 'https://app.example.com/done',
locale: 'pt-BR',
expiresInMinutes: 60,
});
console.log('Client Secret:', session.clientSecret);
// Em HML: session.sandbox.otpCode contém o código para testes
const result = await client.signingSessions.waitForCompletion(session.sessionId);
console.log('Status:', result.status); // COMPLETED
console.log('Evidence ID:', result.evidenceId);
session = client.signing_sessions.create(CreateSigningSessionRequest(
purpose='DOCUMENT_SIGNATURE',
policy=Policy(profile='CLICK_PLUS_OTP'),
signer=Signer(name='Maria Souza', email='maria@example.com', user_external_id='user-002'),
document=InlineDocument(content=pdf_base64, filename='contrato.pdf'),
return_url='https://app.example.com/done',
locale='pt-BR',
expires_in_minutes=60,
))
print('Client Secret:', session.client_secret)
# Em HML: session.sandbox.otp_code
result = client.signing_sessions.wait_for_completion(session.session_id)
print('Status:', result.status) # COMPLETED
print('Evidence ID:', result.evidence_id)
session, err := client.SigningSessions.Create(ctx, &signdocs.CreateSigningSessionRequest{
Purpose: signdocs.PurposeDocumentSignature,
Policy: signdocs.Policy{Profile: signdocs.PolicyProfileClickPlusOTP},
Signer: signdocs.Signer{Name: "Maria Souza", Email: "maria@example.com", UserExternalID: "user-002"},
Document: &signdocs.DocumentInline{Content: pdfBase64, Filename: "contrato.pdf"},
ReturnURL: "https://app.example.com/done",
Locale: "pt-BR",
ExpiresInMinutes: 60,
})
if err != nil { log.Fatal(err) }
fmt.Println("Client Secret:", session.ClientSecret)
// Em HML: session.Sandbox.OTPCode
result, err := client.SigningSessions.WaitForCompletion(ctx, session.SessionID)
if err != nil { log.Fatal(err) }
fmt.Println("Status:", result.Status) // COMPLETED
fmt.Println("Evidence ID:", result.EvidenceID)
String pdfBase64 = Base64.getEncoder().encodeToString(Files.readAllBytes(Path.of("contrato.pdf")));
CreateSigningSessionRequest request = new CreateSigningSessionRequest();
request.purpose = "DOCUMENT_SIGNATURE";
request.policy = new Policy("CLICK_PLUS_OTP");
request.signer = new Signer("Maria Souza", "maria@example.com", "user-002");
request.document = new CreateSigningSessionRequest.InlineDocument(pdfBase64, "contrato.pdf");
request.returnUrl = "https://app.example.com/done";
request.locale = "pt-BR";
request.expiresInMinutes = 60;
SigningSession session = client.signingSessions().create(request);
System.out.println("Client Secret: " + session.clientSecret);
// Em HML: session.sandbox.otpCode
SigningSessionStatusResponse result = client.signingSessions().waitForCompletion(session.sessionId);
System.out.println("Status: " + result.status); // COMPLETED
System.out.println("Evidence ID: " + result.evidenceId);
$pdfBase64 = base64_encode(file_get_contents('contrato.pdf'));
$session = $client->signingSessions->create(new CreateSigningSessionRequest(
purpose: 'DOCUMENT_SIGNATURE',
policy: new Policy(profile: 'CLICK_PLUS_OTP'),
signer: new Signer(name: 'Maria Souza', email: 'maria@example.com', userExternalId: 'user-002'),
document: ['content' => $pdfBase64, 'filename' => 'contrato.pdf'],
returnUrl: 'https://app.example.com/done',
locale: 'pt-BR',
expiresInMinutes: 60,
));
echo "Client Secret: " . $session->clientSecret . "\n";
// Em HML: $session->sandbox->otpCode
$result = $client->signingSessions->waitForCompletion($session->sessionId);
echo "Status: " . $result->status . "\n"; // COMPLETED
echo "Evidence ID: " . $result->evidenceId . "\n";
var pdfBase64 = Convert.ToBase64String(await File.ReadAllBytesAsync("contrato.pdf"));
var session = await client.SigningSessions.CreateAsync(new CreateSigningSessionRequest
{
Purpose = "DOCUMENT_SIGNATURE",
Policy = new Policy { Profile = "CLICK_PLUS_OTP" },
Signer = new Signer { Name = "Maria Souza", Email = "maria@example.com", UserExternalId = "user-002" },
Document = new InlineDocument { Content = pdfBase64, Filename = "contrato.pdf" },
ReturnUrl = "https://app.example.com/done",
Locale = "pt-BR",
ExpiresInMinutes = 60,
});
Console.WriteLine($"Client Secret: {session.ClientSecret}");
// Em HML: session.Sandbox.OtpCode
var result = await client.SigningSessions.WaitForCompletionAsync(session.SessionId);
Console.WriteLine($"Status: {result.Status}"); // COMPLETED
Console.WriteLine($"Evidence ID: {result.EvidenceId}");
Avance as etapas manualmente: aceitar → OTP challenge dispara → verificar OTP.
const session = await client.signingSessions.create({
purpose: 'DOCUMENT_SIGNATURE',
policy: { profile: 'CLICK_PLUS_OTP' },
signer: { name: 'Maria Souza', email: 'maria@example.com', userExternalId: 'user-002' },
document: { content: pdfBase64, filename: 'contrato.pdf' },
});
const baseUrl = process.env.SIGNDOCS_BASE_URL;
const headers = {
'Authorization': `Bearer ${session.clientSecret}`,
'Content-Type': 'application/json',
};
// Passo 1: Aceitar o documento (dispara envio de OTP automaticamente)
const acceptRes = await fetch(
`${baseUrl}/v1/signing-sessions/${session.sessionId}/advance`,
{ method: 'POST', headers, body: JSON.stringify({ action: 'accept' }) },
);
const acceptBody = await acceptRes.json();
console.log('Após accept — status:', acceptBody.status); // AWAITING_OTP
// Em HML: o código OTP vem na resposta
const otpCode = acceptBody.sandbox?.otpCode;
console.log('OTP (sandbox):', otpCode);
// Passo 2: Verificar OTP
const verifyRes = await fetch(
`${baseUrl}/v1/signing-sessions/${session.sessionId}/advance`,
{ method: 'POST', headers, body: JSON.stringify({ action: 'verify_otp', otpCode }) },
);
const verifyBody = await verifyRes.json();
console.log('Status:', verifyBody.status); // COMPLETED
console.log('Evidence ID:', verifyBody.evidenceId);
import requests
session = client.signing_sessions.create(CreateSigningSessionRequest(
purpose='DOCUMENT_SIGNATURE',
policy=Policy(profile='CLICK_PLUS_OTP'),
signer=Signer(name='Maria Souza', email='maria@example.com', user_external_id='user-002'),
document=InlineDocument(content=pdf_base64, filename='contrato.pdf'),
))
base_url = os.environ['SIGNDOCS_BASE_URL']
headers = {'Authorization': f'Bearer {session.client_secret}', 'Content-Type': 'application/json'}
# Passo 1: Aceitar
accept_res = requests.post(
f'{base_url}/v1/signing-sessions/{session.session_id}/advance',
headers=headers, json={'action': 'accept'})
accept_body = accept_res.json()
otp_code = accept_body.get('sandbox', {}).get('otpCode')
print('OTP (sandbox):', otp_code)
# Passo 2: Verificar OTP
verify_res = requests.post(
f'{base_url}/v1/signing-sessions/{session.session_id}/advance',
headers=headers, json={'action': 'verify_otp', 'otpCode': otp_code})
verify_body = verify_res.json()
print('Status:', verify_body['status']) # COMPLETED
print('Evidence ID:', verify_body['evidenceId'])
session, _ := client.SigningSessions.Create(ctx, &signdocs.CreateSigningSessionRequest{
Purpose: signdocs.PurposeDocumentSignature,
Policy: signdocs.Policy{Profile: signdocs.PolicyProfileClickPlusOTP},
Signer: signdocs.Signer{Name: "Maria Souza", Email: "maria@example.com", UserExternalID: "user-002"},
Document: &signdocs.DocumentInline{Content: pdfBase64, Filename: "contrato.pdf"},
})
baseURL := os.Getenv("SIGNDOCS_BASE_URL")
advanceURL := fmt.Sprintf("%s/v1/signing-sessions/%s/advance", baseURL, session.SessionID)
headers := map[string]string{
"Authorization": "Bearer " + session.ClientSecret,
"Content-Type": "application/json",
}
// Passo 1: Aceitar
acceptBody, _ := json.Marshal(map[string]string{"action": "accept"})
acceptReq, _ := http.NewRequest("POST", advanceURL, bytes.NewReader(acceptBody))
for k, v := range headers { acceptReq.Header.Set(k, v) }
acceptResp, _ := http.DefaultClient.Do(acceptReq)
var acceptResult map[string]interface{}
json.NewDecoder(acceptResp.Body).Decode(&acceptResult)
otpCode := acceptResult["sandbox"].(map[string]interface{})["otpCode"].(string)
// Passo 2: Verificar OTP
verifyBody, _ := json.Marshal(map[string]string{"action": "verify_otp", "otpCode": otpCode})
verifyReq, _ := http.NewRequest("POST", advanceURL, bytes.NewReader(verifyBody))
for k, v := range headers { verifyReq.Header.Set(k, v) }
verifyResp, _ := http.DefaultClient.Do(verifyReq)
var verifyResult map[string]interface{}
json.NewDecoder(verifyResp.Body).Decode(&verifyResult)
fmt.Println("Status:", verifyResult["status"]) // COMPLETED
fmt.Println("Evidence ID:", verifyResult["evidenceId"])
SigningSession session = client.signingSessions().create(request); // (ver hosted acima)
HttpClient http = HttpClient.newHttpClient();
String advanceUrl = System.getenv("SIGNDOCS_BASE_URL")
+ "/v1/signing-sessions/" + session.sessionId + "/advance";
// Passo 1: Aceitar
HttpRequest acceptReq = HttpRequest.newBuilder().uri(URI.create(advanceUrl))
.header("Authorization", "Bearer " + session.clientSecret)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"action\":\"accept\"}")).build();
String acceptBody = http.send(acceptReq, HttpResponse.BodyHandlers.ofString()).body();
// Extrair otpCode do sandbox (use sua lib JSON preferida)
// Passo 2: Verificar OTP
HttpRequest verifyReq = HttpRequest.newBuilder().uri(URI.create(advanceUrl))
.header("Authorization", "Bearer " + session.clientSecret)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"action\":\"verify_otp\",\"otpCode\":\"" + otpCode + "\"}")).build();
String verifyBody = http.send(verifyReq, HttpResponse.BodyHandlers.ofString()).body();
System.out.println(verifyBody); // {"status":"COMPLETED","evidenceId":"..."}
$session = $client->signingSessions->create(/* ... ver hosted acima ... */);
$advanceUrl = getenv('SIGNDOCS_BASE_URL')
. '/v1/signing-sessions/' . $session->sessionId . '/advance';
$headers = [
'Authorization: Bearer ' . $session->clientSecret,
'Content-Type: application/json',
];
// Passo 1: Aceitar
$ch = curl_init($advanceUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => json_encode(['action' => 'accept']),
CURLOPT_RETURNTRANSFER => true,
]);
$acceptBody = json_decode(curl_exec($ch));
$otpCode = $acceptBody->sandbox->otpCode ?? null;
// Passo 2: Verificar OTP
$ch = curl_init($advanceUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => json_encode(['action' => 'verify_otp', 'otpCode' => $otpCode]),
CURLOPT_RETURNTRANSFER => true,
]);
$verifyBody = json_decode(curl_exec($ch));
echo "Status: " . $verifyBody->status . "\n"; // COMPLETED
echo "Evidence ID: " . $verifyBody->evidenceId . "\n";
var session = await client.SigningSessions.CreateAsync(/* ... ver hosted acima ... */);
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", session.ClientSecret);
var advanceUrl = $"{Environment.GetEnvironmentVariable("SIGNDOCS_BASE_URL")}/v1/signing-sessions/{session.SessionId}/advance";
// Passo 1: Aceitar
var acceptRes = await http.PostAsync(advanceUrl,
new StringContent("{\"action\":\"accept\"}", System.Text.Encoding.UTF8, "application/json"));
var acceptJson = System.Text.Json.JsonDocument.Parse(await acceptRes.Content.ReadAsStringAsync());
var otpCode = acceptJson.RootElement.GetProperty("sandbox").GetProperty("otpCode").GetString();
// Passo 2: Verificar OTP
var verifyRes = await http.PostAsync(advanceUrl,
new StringContent($"{{\"action\":\"verify_otp\",\"otpCode\":\"{otpCode}\"}}",
System.Text.Encoding.UTF8, "application/json"));
Console.WriteLine(await verifyRes.Content.ReadAsStringAsync());
// {"status":"COMPLETED","evidenceId":"..."}
Sandbox: Em HML, o
sandbox.otpCodeé retornado na resposta doaccept. Em produção, o código é enviado por email/SMS e o signatário precisa digitá-lo.
O signatário passa por verificação facial via liveness no checkout hospedado.
Backend: create session ──> clientSecret
│
Frontend: sd.checkout() ──> popup abre
│
Signatário: liveness facial ──> match biométrico
│
COMPLETED
│
Backend: poll ou webhook ──> evidenceId
Obrigatório:
signer.cpfpara matching biométrico.
A página hospedada executa toda a captura de liveness. Seu backend só cria e espera.
const pdfBase64 = readFileSync('contrato.pdf').toString('base64');
const session = await client.signingSessions.create({
purpose: 'DOCUMENT_SIGNATURE',
policy: { profile: 'BIOMETRIC' },
signer: {
name: 'Carlos Lima',
cpf: '12345678901',
userExternalId: 'user-003',
},
document: { content: pdfBase64, filename: 'contrato.pdf' },
returnUrl: 'https://app.example.com/done',
locale: 'pt-BR',
expiresInMinutes: 60,
});
console.log('Client Secret:', session.clientSecret);
const result = await client.signingSessions.waitForCompletion(session.sessionId);
console.log('Status:', result.status); // COMPLETED
console.log('Evidence ID:', result.evidenceId);
session = client.signing_sessions.create(CreateSigningSessionRequest(
purpose='DOCUMENT_SIGNATURE',
policy=Policy(profile='BIOMETRIC'),
signer=Signer(name='Carlos Lima', cpf='12345678901', user_external_id='user-003'),
document=InlineDocument(content=pdf_base64, filename='contrato.pdf'),
return_url='https://app.example.com/done',
locale='pt-BR',
expires_in_minutes=60,
))
print('Client Secret:', session.client_secret)
result = client.signing_sessions.wait_for_completion(session.session_id)
print('Status:', result.status) # COMPLETED
print('Evidence ID:', result.evidence_id)
session, err := client.SigningSessions.Create(ctx, &signdocs.CreateSigningSessionRequest{
Purpose: signdocs.PurposeDocumentSignature,
Policy: signdocs.Policy{Profile: signdocs.PolicyProfileBiometric},
Signer: signdocs.Signer{Name: "Carlos Lima", CPF: "12345678901", UserExternalID: "user-003"},
Document: &signdocs.DocumentInline{Content: pdfBase64, Filename: "contrato.pdf"},
ReturnURL: "https://app.example.com/done",
Locale: "pt-BR",
ExpiresInMinutes: 60,
})
if err != nil { log.Fatal(err) }
fmt.Println("Client Secret:", session.ClientSecret)
result, err := client.SigningSessions.WaitForCompletion(ctx, session.SessionID)
if err != nil { log.Fatal(err) }
fmt.Println("Status:", result.Status) // COMPLETED
fmt.Println("Evidence ID:", result.EvidenceID)
String pdfBase64 = Base64.getEncoder().encodeToString(Files.readAllBytes(Path.of("contrato.pdf")));
CreateSigningSessionRequest request = new CreateSigningSessionRequest();
request.purpose = "DOCUMENT_SIGNATURE";
request.policy = new Policy("BIOMETRIC");
request.signer = new Signer("Carlos Lima", "user-003");
request.signer.cpf = "12345678901";
request.document = new CreateSigningSessionRequest.InlineDocument(pdfBase64, "contrato.pdf");
request.returnUrl = "https://app.example.com/done";
request.locale = "pt-BR";
request.expiresInMinutes = 60;
SigningSession session = client.signingSessions().create(request);
System.out.println("Client Secret: " + session.clientSecret);
SigningSessionStatusResponse result = client.signingSessions().waitForCompletion(session.sessionId);
System.out.println("Status: " + result.status); // COMPLETED
System.out.println("Evidence ID: " + result.evidenceId);
$pdfBase64 = base64_encode(file_get_contents('contrato.pdf'));
$session = $client->signingSessions->create(new CreateSigningSessionRequest(
purpose: 'DOCUMENT_SIGNATURE',
policy: new Policy(profile: 'BIOMETRIC'),
signer: new Signer(name: 'Carlos Lima', cpf: '12345678901', userExternalId: 'user-003'),
document: ['content' => $pdfBase64, 'filename' => 'contrato.pdf'],
returnUrl: 'https://app.example.com/done',
locale: 'pt-BR',
expiresInMinutes: 60,
));
echo "Client Secret: " . $session->clientSecret . "\n";
$result = $client->signingSessions->waitForCompletion($session->sessionId);
echo "Status: " . $result->status . "\n"; // COMPLETED
echo "Evidence ID: " . $result->evidenceId . "\n";
var pdfBase64 = Convert.ToBase64String(await File.ReadAllBytesAsync("contrato.pdf"));
var session = await client.SigningSessions.CreateAsync(new CreateSigningSessionRequest
{
Purpose = "DOCUMENT_SIGNATURE",
Policy = new Policy { Profile = "BIOMETRIC" },
Signer = new Signer { Name = "Carlos Lima", Cpf = "12345678901", UserExternalId = "user-003" },
Document = new InlineDocument { Content = pdfBase64, Filename = "contrato.pdf" },
ReturnUrl = "https://app.example.com/done",
Locale = "pt-BR",
ExpiresInMinutes = 60,
});
Console.WriteLine($"Client Secret: {session.ClientSecret}");
var result = await client.SigningSessions.WaitForCompletionAsync(session.SessionId);
Console.WriteLine($"Status: {result.Status}"); // COMPLETED
Console.WriteLine($"Evidence ID: {result.EvidenceId}");
Para integrações server-to-server: inicie o liveness, aguarde o resultado, complete o match.
const session = await client.signingSessions.create({
purpose: 'DOCUMENT_SIGNATURE',
policy: { profile: 'BIOMETRIC' },
signer: { name: 'Carlos Lima', cpf: '12345678901', userExternalId: 'user-003' },
document: { content: pdfBase64, filename: 'contrato.pdf' },
});
const baseUrl = process.env.SIGNDOCS_BASE_URL;
const headers = {
'Authorization': `Bearer ${session.clientSecret}`,
'Content-Type': 'application/json',
};
const advanceUrl = `${baseUrl}/v1/signing-sessions/${session.sessionId}/advance`;
// Passo 1: Iniciar liveness — retorna hostedUrl para o signatário
const startRes = await fetch(advanceUrl, {
method: 'POST', headers,
body: JSON.stringify({ action: 'start_liveness' }),
});
const startBody = await startRes.json();
console.log('Hosted URL:', startBody.hostedUrl);
console.log('Liveness Session ID:', startBody.livenessSessionId);
// (Signatário completa a verificação facial na hostedUrl)
// Passo 2: Completar liveness (após o signatário finalizar)
const completeRes = await fetch(advanceUrl, {
method: 'POST', headers,
body: JSON.stringify({
action: 'complete_liveness',
livenessSessionId: startBody.livenessSessionId,
}),
});
const completeBody = await completeRes.json();
console.log('Status:', completeBody.status); // COMPLETED
console.log('Evidence ID:', completeBody.evidenceId);
session = client.signing_sessions.create(CreateSigningSessionRequest(
purpose='DOCUMENT_SIGNATURE',
policy=Policy(profile='BIOMETRIC'),
signer=Signer(name='Carlos Lima', cpf='12345678901', user_external_id='user-003'),
document=InlineDocument(content=pdf_base64, filename='contrato.pdf'),
))
base_url = os.environ['SIGNDOCS_BASE_URL']
headers = {'Authorization': f'Bearer {session.client_secret}', 'Content-Type': 'application/json'}
advance_url = f'{base_url}/v1/signing-sessions/{session.session_id}/advance'
# Passo 1: Iniciar liveness
start_res = requests.post(advance_url, headers=headers, json={'action': 'start_liveness'})
start_body = start_res.json()
print('Hosted URL:', start_body['hostedUrl'])
liveness_session_id = start_body['livenessSessionId']
# (Signatário completa a verificação facial)
# Passo 2: Completar liveness
complete_res = requests.post(advance_url, headers=headers, json={
'action': 'complete_liveness',
'livenessSessionId': liveness_session_id,
})
complete_body = complete_res.json()
print('Status:', complete_body['status']) # COMPLETED
print('Evidence ID:', complete_body['evidenceId'])
session, _ := client.SigningSessions.Create(ctx, &signdocs.CreateSigningSessionRequest{
Purpose: signdocs.PurposeDocumentSignature,
Policy: signdocs.Policy{Profile: signdocs.PolicyProfileBiometric},
Signer: signdocs.Signer{Name: "Carlos Lima", CPF: "12345678901", UserExternalID: "user-003"},
Document: &signdocs.DocumentInline{Content: pdfBase64, Filename: "contrato.pdf"},
})
advanceURL := fmt.Sprintf("%s/v1/signing-sessions/%s/advance",
os.Getenv("SIGNDOCS_BASE_URL"), session.SessionID)
// Passo 1: Iniciar liveness
startBody, _ := json.Marshal(map[string]string{"action": "start_liveness"})
startReq, _ := http.NewRequest("POST", advanceURL, bytes.NewReader(startBody))
startReq.Header.Set("Authorization", "Bearer "+session.ClientSecret)
startReq.Header.Set("Content-Type", "application/json")
startResp, _ := http.DefaultClient.Do(startReq)
var startResult map[string]interface{}
json.NewDecoder(startResp.Body).Decode(&startResult)
livenessSessionID := startResult["livenessSessionId"].(string)
// (Signatário completa a verificação facial)
// Passo 2: Completar liveness
completeBody, _ := json.Marshal(map[string]interface{}{
"action": "complete_liveness", "livenessSessionId": livenessSessionID,
})
completeReq, _ := http.NewRequest("POST", advanceURL, bytes.NewReader(completeBody))
completeReq.Header.Set("Authorization", "Bearer "+session.ClientSecret)
completeReq.Header.Set("Content-Type", "application/json")
completeResp, _ := http.DefaultClient.Do(completeReq)
var completeResult map[string]interface{}
json.NewDecoder(completeResp.Body).Decode(&completeResult)
fmt.Println("Status:", completeResult["status"]) // COMPLETED
fmt.Println("Evidence ID:", completeResult["evidenceId"])
SigningSession session = client.signingSessions().create(request); // (ver hosted acima)
HttpClient http = HttpClient.newHttpClient();
String advanceUrl = System.getenv("SIGNDOCS_BASE_URL")
+ "/v1/signing-sessions/" + session.sessionId + "/advance";
// Passo 1: Iniciar liveness
HttpRequest startReq = HttpRequest.newBuilder().uri(URI.create(advanceUrl))
.header("Authorization", "Bearer " + session.clientSecret)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"action\":\"start_liveness\"}")).build();
String startBody = http.send(startReq, HttpResponse.BodyHandlers.ofString()).body();
// Extrair hostedUrl e livenessSessionId
// (Signatário completa a verificação facial)
// Passo 2: Completar liveness
HttpRequest completeReq = HttpRequest.newBuilder().uri(URI.create(advanceUrl))
.header("Authorization", "Bearer " + session.clientSecret)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"action\":\"complete_liveness\",\"livenessSessionId\":\"" + livenessSessionId + "\"}")).build();
String completeBody = http.send(completeReq, HttpResponse.BodyHandlers.ofString()).body();
System.out.println(completeBody); // {"status":"COMPLETED","evidenceId":"..."}
$session = $client->signingSessions->create(/* ... ver hosted acima ... */);
$advanceUrl = getenv('SIGNDOCS_BASE_URL')
. '/v1/signing-sessions/' . $session->sessionId . '/advance';
$headers = [
'Authorization: Bearer ' . $session->clientSecret,
'Content-Type: application/json',
];
// Passo 1: Iniciar liveness
$ch = curl_init($advanceUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => json_encode(['action' => 'start_liveness']),
CURLOPT_RETURNTRANSFER => true,
]);
$startBody = json_decode(curl_exec($ch));
$livenessSessionId = $startBody->livenessSessionId;
// (Signatário completa a verificação facial)
// Passo 2: Completar liveness
$ch = curl_init($advanceUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => json_encode([
'action' => 'complete_liveness',
'livenessSessionId' => $livenessSessionId,
]),
CURLOPT_RETURNTRANSFER => true,
]);
$completeBody = json_decode(curl_exec($ch));
echo "Status: " . $completeBody->status . "\n"; // COMPLETED
echo "Evidence ID: " . $completeBody->evidenceId . "\n";
var session = await client.SigningSessions.CreateAsync(/* ... ver hosted acima ... */);
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", session.ClientSecret);
var advanceUrl = $"{Environment.GetEnvironmentVariable("SIGNDOCS_BASE_URL")}/v1/signing-sessions/{session.SessionId}/advance";
// Passo 1: Iniciar liveness
var startRes = await http.PostAsync(advanceUrl,
new StringContent("{\"action\":\"start_liveness\"}", System.Text.Encoding.UTF8, "application/json"));
var startJson = System.Text.Json.JsonDocument.Parse(await startRes.Content.ReadAsStringAsync());
var livenessSessionId = startJson.RootElement.GetProperty("livenessSessionId").GetString();
// (Signatário completa a verificação facial)
// Passo 2: Completar liveness
var completeRes = await http.PostAsync(advanceUrl,
new StringContent($"{{\"action\":\"complete_liveness\",\"livenessSessionId\":\"{livenessSessionId}\"}}",
System.Text.Encoding.UTF8, "application/json"));
Console.WriteLine(await completeRes.Content.ReadAsStringAsync());
// {"status":"COMPLETED","evidenceId":"..."}
HML: A etapa de liveness pode ser simulada em sandbox. Em produção, requer câmera do dispositivo.
Assinatura via certificado digital ICP-Brasil. O fluxo usa protocolo de duas fases: o servidor prepara um hash, você assina localmente com a chave privada.
Backend: create session ──> clientSecret
│
Frontend: sd.checkout() ──> popup abre
│
Signatário: seleciona certificado ──> prepare_signing
│
Servidor: hashToSign ──> assinar localmente
│
complete_signing ──> COMPLETED
│
Backend: poll ou webhook ──> evidenceId
Pré-requisito: Certificado A1 (
certificate.pem) e chave privada (private-key.pem). Consulte o Guia de Assinatura Digital para detalhes sobre o protocolo.
A página hospedada gerencia a seleção do certificado e a assinatura local. Seu backend só cria e espera.
const pdfBase64 = readFileSync('contrato.pdf').toString('base64');
const session = await client.signingSessions.create({
purpose: 'DOCUMENT_SIGNATURE',
policy: { profile: 'DIGITAL_CERTIFICATE' },
signer: {
name: 'Ana Pereira',
cpf: '98765432100',
userExternalId: 'user-004',
},
document: { content: pdfBase64, filename: 'contrato.pdf' },
returnUrl: 'https://app.example.com/done',
locale: 'pt-BR',
expiresInMinutes: 60,
});
console.log('Client Secret:', session.clientSecret);
const result = await client.signingSessions.waitForCompletion(session.sessionId);
console.log('Status:', result.status); // COMPLETED
console.log('Evidence ID:', result.evidenceId);
session = client.signing_sessions.create(CreateSigningSessionRequest(
purpose='DOCUMENT_SIGNATURE',
policy=Policy(profile='DIGITAL_CERTIFICATE'),
signer=Signer(name='Ana Pereira', cpf='98765432100', user_external_id='user-004'),
document=InlineDocument(content=pdf_base64, filename='contrato.pdf'),
return_url='https://app.example.com/done',
locale='pt-BR',
expires_in_minutes=60,
))
print('Client Secret:', session.client_secret)
result = client.signing_sessions.wait_for_completion(session.session_id)
print('Status:', result.status) # COMPLETED
print('Evidence ID:', result.evidence_id)
session, err := client.SigningSessions.Create(ctx, &signdocs.CreateSigningSessionRequest{
Purpose: signdocs.PurposeDocumentSignature,
Policy: signdocs.Policy{Profile: signdocs.PolicyProfileDigitalCertificate},
Signer: signdocs.Signer{Name: "Ana Pereira", CPF: "98765432100", UserExternalID: "user-004"},
Document: &signdocs.DocumentInline{Content: pdfBase64, Filename: "contrato.pdf"},
ReturnURL: "https://app.example.com/done",
Locale: "pt-BR",
ExpiresInMinutes: 60,
})
if err != nil { log.Fatal(err) }
fmt.Println("Client Secret:", session.ClientSecret)
result, err := client.SigningSessions.WaitForCompletion(ctx, session.SessionID)
if err != nil { log.Fatal(err) }
fmt.Println("Status:", result.Status) // COMPLETED
fmt.Println("Evidence ID:", result.EvidenceID)
String pdfBase64 = Base64.getEncoder().encodeToString(Files.readAllBytes(Path.of("contrato.pdf")));
CreateSigningSessionRequest request = new CreateSigningSessionRequest();
request.purpose = "DOCUMENT_SIGNATURE";
request.policy = new Policy("DIGITAL_CERTIFICATE");
request.signer = new Signer("Ana Pereira", "user-004");
request.signer.cpf = "98765432100";
request.document = new CreateSigningSessionRequest.InlineDocument(pdfBase64, "contrato.pdf");
request.returnUrl = "https://app.example.com/done";
request.locale = "pt-BR";
request.expiresInMinutes = 60;
SigningSession session = client.signingSessions().create(request);
System.out.println("Client Secret: " + session.clientSecret);
SigningSessionStatusResponse result = client.signingSessions().waitForCompletion(session.sessionId);
System.out.println("Status: " + result.status); // COMPLETED
System.out.println("Evidence ID: " + result.evidenceId);
$pdfBase64 = base64_encode(file_get_contents('contrato.pdf'));
$session = $client->signingSessions->create(new CreateSigningSessionRequest(
purpose: 'DOCUMENT_SIGNATURE',
policy: new Policy(profile: 'DIGITAL_CERTIFICATE'),
signer: new Signer(name: 'Ana Pereira', cpf: '98765432100', userExternalId: 'user-004'),
document: ['content' => $pdfBase64, 'filename' => 'contrato.pdf'],
returnUrl: 'https://app.example.com/done',
locale: 'pt-BR',
expiresInMinutes: 60,
));
echo "Client Secret: " . $session->clientSecret . "\n";
$result = $client->signingSessions->waitForCompletion($session->sessionId);
echo "Status: " . $result->status . "\n"; // COMPLETED
echo "Evidence ID: " . $result->evidenceId . "\n";
var pdfBase64 = Convert.ToBase64String(await File.ReadAllBytesAsync("contrato.pdf"));
var session = await client.SigningSessions.CreateAsync(new CreateSigningSessionRequest
{
Purpose = "DOCUMENT_SIGNATURE",
Policy = new Policy { Profile = "DIGITAL_CERTIFICATE" },
Signer = new Signer { Name = "Ana Pereira", Cpf = "98765432100", UserExternalId = "user-004" },
Document = new InlineDocument { Content = pdfBase64, Filename = "contrato.pdf" },
ReturnUrl = "https://app.example.com/done",
Locale = "pt-BR",
ExpiresInMinutes = 60,
});
Console.WriteLine($"Client Secret: {session.ClientSecret}");
var result = await client.SigningSessions.WaitForCompletionAsync(session.SessionId);
Console.WriteLine($"Status: {result.Status}"); // COMPLETED
Console.WriteLine($"Evidence ID: {result.EvidenceId}");
Para assinatura server-side com certificado A1. Protocolo de 2 fases: prepare_signing → assinar localmente → complete_signing.
Algoritmo: RSASSA-PKCS1-v1_5 com SHA-256. O
hashToSignjá é o digest — não aplique hash novamente.
import { createPrivateKey, sign } from 'crypto';
import { readFileSync } from 'fs';
const session = await client.signingSessions.create({
purpose: 'DOCUMENT_SIGNATURE',
policy: { profile: 'DIGITAL_CERTIFICATE' },
signer: { name: 'Ana Pereira', cpf: '98765432100', userExternalId: 'user-004' },
document: { content: pdfBase64, filename: 'contrato.pdf' },
});
const baseUrl = process.env.SIGNDOCS_BASE_URL;
const headers = {
'Authorization': `Bearer ${session.clientSecret}`,
'Content-Type': 'application/json',
};
const advanceUrl = `${baseUrl}/v1/signing-sessions/${session.sessionId}/advance`;
// Passo 1: Aceitar o documento
await fetch(advanceUrl, {
method: 'POST', headers,
body: JSON.stringify({ action: 'accept' }),
});
// Passo 2: Preparar assinatura — enviar certificado, receber hash
const certPem = readFileSync('certificate.pem', 'utf-8');
const prepareRes = await fetch(advanceUrl, {
method: 'POST', headers,
body: JSON.stringify({
action: 'prepare_signing',
certificateChainPems: [certPem],
}),
});
const prepareBody = await prepareRes.json();
const { signatureRequestId, hashToSign } = prepareBody;
// Passo 3: Assinar localmente com a chave privada
const privateKey = createPrivateKey(readFileSync('private-key.pem'));
const hashBuffer = Buffer.from(hashToSign, 'hex');
const signature = sign(null, hashBuffer, {
key: privateKey,
padding: 1, // RSA_PKCS1_PADDING
});
const rawSignatureBase64 = signature.toString('base64');
// Passo 4: Completar assinatura
const completeRes = await fetch(advanceUrl, {
method: 'POST', headers,
body: JSON.stringify({
action: 'complete_signing',
signatureRequestId,
rawSignatureBase64,
}),
});
const completeBody = await completeRes.json();
console.log('Status:', completeBody.status); // COMPLETED
console.log('Evidence ID:', completeBody.evidenceId);
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, utils
session = client.signing_sessions.create(CreateSigningSessionRequest(
purpose='DOCUMENT_SIGNATURE',
policy=Policy(profile='DIGITAL_CERTIFICATE'),
signer=Signer(name='Ana Pereira', cpf='98765432100', user_external_id='user-004'),
document=InlineDocument(content=pdf_base64, filename='contrato.pdf'),
))
base_url = os.environ['SIGNDOCS_BASE_URL']
headers = {'Authorization': f'Bearer {session.client_secret}', 'Content-Type': 'application/json'}
advance_url = f'{base_url}/v1/signing-sessions/{session.session_id}/advance'
# Passo 1: Aceitar
requests.post(advance_url, headers=headers, json={'action': 'accept'})
# Passo 2: Preparar assinatura
with open('certificate.pem') as f:
cert_pem = f.read()
prepare_res = requests.post(advance_url, headers=headers, json={
'action': 'prepare_signing',
'certificateChainPems': [cert_pem],
})
prepare_body = prepare_res.json()
signature_request_id = prepare_body['signatureRequestId']
hash_to_sign = bytes.fromhex(prepare_body['hashToSign'])
# Passo 3: Assinar localmente
with open('private-key.pem', 'rb') as f:
private_key = serialization.load_pem_private_key(f.read(), password=None)
raw_signature = private_key.sign(
hash_to_sign,
padding.PKCS1v15(),
utils.Prehashed(hashes.SHA256()),
)
raw_signature_base64 = base64.b64encode(raw_signature).decode()
# Passo 4: Completar assinatura
complete_res = requests.post(advance_url, headers=headers, json={
'action': 'complete_signing',
'signatureRequestId': signature_request_id,
'rawSignatureBase64': raw_signature_base64,
})
complete_body = complete_res.json()
print('Status:', complete_body['status']) # COMPLETED
print('Evidence ID:', complete_body['evidenceId'])
import (
"crypto"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
)
session, _ := client.SigningSessions.Create(ctx, &signdocs.CreateSigningSessionRequest{
Purpose: signdocs.PurposeDocumentSignature,
Policy: signdocs.Policy{Profile: signdocs.PolicyProfileDigitalCertificate},
Signer: signdocs.Signer{Name: "Ana Pereira", CPF: "98765432100", UserExternalID: "user-004"},
Document: &signdocs.DocumentInline{Content: pdfBase64, Filename: "contrato.pdf"},
})
advanceURL := fmt.Sprintf("%s/v1/signing-sessions/%s/advance",
os.Getenv("SIGNDOCS_BASE_URL"), session.SessionID)
doAdvance := func(payload interface{}) map[string]interface{} {
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", advanceURL, bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+session.ClientSecret)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
return result
}
// Passo 1: Aceitar
doAdvance(map[string]string{"action": "accept"})
// Passo 2: Preparar assinatura
certPEM, _ := os.ReadFile("certificate.pem")
prepareResult := doAdvance(map[string]interface{}{
"action": "prepare_signing", "certificateChainPems": []string{string(certPEM)},
})
signatureRequestID := prepareResult["signatureRequestId"].(string)
hashHex := prepareResult["hashToSign"].(string)
hashBytes, _ := hex.DecodeString(hashHex)
// Passo 3: Assinar localmente
keyPEM, _ := os.ReadFile("private-key.pem")
block, _ := pem.Decode(keyPEM)
key, _ := x509.ParsePKCS8PrivateKey(block.Bytes)
rsaKey := key.(*rsa.PrivateKey)
signature, _ := rsa.SignPKCS1v15(rand.Reader, rsaKey, crypto.SHA256, hashBytes)
rawSignatureBase64 := base64.StdEncoding.EncodeToString(signature)
// Passo 4: Completar assinatura
completeResult := doAdvance(map[string]interface{}{
"action": "complete_signing",
"signatureRequestId": signatureRequestID,
"rawSignatureBase64": rawSignatureBase64,
})
fmt.Println("Status:", completeResult["status"]) // COMPLETED
fmt.Println("Evidence ID:", completeResult["evidenceId"])
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
SigningSession session = client.signingSessions().create(request); // (ver hosted acima)
HttpClient http = HttpClient.newHttpClient();
String advanceUrl = System.getenv("SIGNDOCS_BASE_URL")
+ "/v1/signing-sessions/" + session.sessionId + "/advance";
// Passo 1: Aceitar
http.send(HttpRequest.newBuilder().uri(URI.create(advanceUrl))
.header("Authorization", "Bearer " + session.clientSecret)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"action\":\"accept\"}")).build(),
HttpResponse.BodyHandlers.ofString());
// Passo 2: Preparar assinatura
String certPem = Files.readString(Path.of("certificate.pem"));
HttpRequest prepareReq = HttpRequest.newBuilder().uri(URI.create(advanceUrl))
.header("Authorization", "Bearer " + session.clientSecret)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"action\":\"prepare_signing\",\"certificateChainPems\":[\"" + certPem.replace("\n","\\n") + "\"]}"))
.build();
String prepareBody = http.send(prepareReq, HttpResponse.BodyHandlers.ofString()).body();
// Extrair signatureRequestId e hashToSign (use sua lib JSON preferida)
// Passo 3: Assinar localmente (NONEwithRSA — hash já é digest)
byte[] hashBytes = hexToBytes(hashToSign);
Signature sig = Signature.getInstance("NONEwithRSA");
sig.initSign(privateKey); // Carregar de private-key.pem
// Aplicar DigestInfo wrapping: SHA-256 OID prefix + hashBytes
byte[] digestInfo = wrapDigestInfo(hashBytes);
sig.update(digestInfo);
String rawSignatureBase64 = Base64.getEncoder().encodeToString(sig.sign());
// Passo 4: Completar assinatura
HttpRequest completeReq = HttpRequest.newBuilder().uri(URI.create(advanceUrl))
.header("Authorization", "Bearer " + session.clientSecret)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"action\":\"complete_signing\",\"signatureRequestId\":\"" + signatureRequestId
+ "\",\"rawSignatureBase64\":\"" + rawSignatureBase64 + "\"}"))
.build();
System.out.println(http.send(completeReq, HttpResponse.BodyHandlers.ofString()).body());
$session = $client->signingSessions->create(/* ... ver hosted acima ... */);
$advanceUrl = getenv('SIGNDOCS_BASE_URL')
. '/v1/signing-sessions/' . $session->sessionId . '/advance';
$headers = [
'Authorization: Bearer ' . $session->clientSecret,
'Content-Type: application/json',
];
$advance = function(array $payload) use ($advanceUrl, $headers) {
$ch = curl_init($advanceUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
]);
return json_decode(curl_exec($ch));
};
// Passo 1: Aceitar
$advance(['action' => 'accept']);
// Passo 2: Preparar assinatura
$certPem = file_get_contents('certificate.pem');
$prepareBody = $advance([
'action' => 'prepare_signing',
'certificateChainPems' => [$certPem],
]);
// Passo 3: Assinar localmente
$privateKey = openssl_pkey_get_private(file_get_contents('private-key.pem'));
$hashBytes = hex2bin($prepareBody->hashToSign);
openssl_private_encrypt($hashBytes, $signature, $privateKey, OPENSSL_PKCS1_PADDING);
$rawSignatureBase64 = base64_encode($signature);
// Passo 4: Completar assinatura
$completeBody = $advance([
'action' => 'complete_signing',
'signatureRequestId' => $prepareBody->signatureRequestId,
'rawSignatureBase64' => $rawSignatureBase64,
]);
echo "Status: " . $completeBody->status . "\n"; // COMPLETED
echo "Evidence ID: " . $completeBody->evidenceId . "\n";
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
var session = await client.SigningSessions.CreateAsync(/* ... ver hosted acima ... */);
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", session.ClientSecret);
var advanceUrl = $"{Environment.GetEnvironmentVariable("SIGNDOCS_BASE_URL")}/v1/signing-sessions/{session.SessionId}/advance";
async Task<System.Text.Json.JsonDocument> AdvanceAsync(object payload)
{
var res = await http.PostAsync(advanceUrl,
new StringContent(System.Text.Json.JsonSerializer.Serialize(payload),
System.Text.Encoding.UTF8, "application/json"));
return System.Text.Json.JsonDocument.Parse(await res.Content.ReadAsStringAsync());
}
// Passo 1: Aceitar
await AdvanceAsync(new { action = "accept" });
// Passo 2: Preparar assinatura
var certPem = await File.ReadAllTextAsync("certificate.pem");
var prepareJson = await AdvanceAsync(new {
action = "prepare_signing",
certificateChainPems = new[] { certPem },
});
var signatureRequestId = prepareJson.RootElement.GetProperty("signatureRequestId").GetString()!;
var hashHex = prepareJson.RootElement.GetProperty("hashToSign").GetString()!;
var hashBytes = Convert.FromHexString(hashHex);
// Passo 3: Assinar localmente
using var rsa = RSA.Create();
rsa.ImportFromPem(await File.ReadAllTextAsync("private-key.pem"));
var signature = rsa.SignHash(hashBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var rawSignatureBase64 = Convert.ToBase64String(signature);
// Passo 4: Completar assinatura
var completeJson = await AdvanceAsync(new {
action = "complete_signing",
signatureRequestId,
rawSignatureBase64,
});
Console.WriteLine($"Status: {completeJson.RootElement.GetProperty("status")}"); // COMPLETED
Console.WriteLine($"Evidence ID: {completeJson.RootElement.GetProperty("evidenceId")}");
Perfil consolidado para empréstimos consignados INSS (Nota Técnica 65). Combina biometria facial com verificação SERPRO e fallback automático para foto de documento quando o SERPRO não possui dados biométricos.
nt65ComplianceEnabled, serpro.enabled, documentExtractionEnabledsigner.cpf, signer.birthDate, signer.namePURPOSE_DISCLOSURE (se NT65 habilitado)
│
▼
BIOMETRIC_LIVENESS captura facial
│
▼
BIOMETRIC_MATCH comparação facial
│
▼
SERPRO_IDENTITY_CHECK verificação biográfica + biométrica
│
├── SERPRO OK ──────────────────────> COMPLETED
│
└── biometricAvailable: false ──┐
(mas nome+nascimento OK) │
▼
DOCUMENT_PHOTO_MATCH (era DORMANT → PENDING)
│
▼
COMPLETED
O fallback dispara somente quando o SERPRO retorna que:
- biometricAvailable: false (CPF não possui biometria no SERPRO)
- nameMatch: true (dados biográficos conferem)
- birthDateMatch: true (data de nascimento confere)
Nesse caso:
1. Etapa SERPRO → status SKIPPED com skipReason: 'BIOMETRIC_UNAVAILABLE'
2. Etapa DOCUMENT_PHOTO_MATCH (dormant) → status PENDING
3. Webhook TRANSACTION.FALLBACK emitido
O fallback NÃO dispara quando: - Score biométrico é baixo (falha normal) - Dados biográficos não conferem (CPF/nome/nascimento divergentes)
A página hospedada gerencia todas as etapas, incluindo o fallback. Seu backend só cria e espera.
const pdfBase64 = readFileSync('contrato.pdf').toString('base64');
const session = await client.signingSessions.create({
purpose: 'DOCUMENT_SIGNATURE',
policy: { profile: 'BIOMETRIC_SERPRO_AUTO_FALLBACK' },
signer: {
name: 'Roberto Santos',
cpf: '12345678901',
birthDate: '1985-03-15',
userExternalId: 'user-005',
},
document: { content: pdfBase64, filename: 'contrato-consignado.pdf' },
returnUrl: 'https://app.example.com/done',
locale: 'pt-BR',
expiresInMinutes: 60,
});
console.log('Session ID:', session.sessionId);
console.log('Client Secret:', session.clientSecret);
// A página hospedada cuida de:
// - PURPOSE_DISCLOSURE (reconhecimento de coleta biométrica)
// - BIOMETRIC_LIVENESS (captura facial)
// - BIOMETRIC_MATCH (comparação)
// - SERPRO_IDENTITY_CHECK (verificação)
// - DOCUMENT_PHOTO_MATCH (se fallback ativado — foto do documento)
const result = await client.signingSessions.waitForCompletion(session.sessionId);
console.log('Status:', result.status); // COMPLETED
console.log('Evidence ID:', result.evidenceId);
session = client.signing_sessions.create(CreateSigningSessionRequest(
purpose='DOCUMENT_SIGNATURE',
policy=Policy(profile='BIOMETRIC_SERPRO_AUTO_FALLBACK'),
signer=Signer(
name='Roberto Santos',
cpf='12345678901',
birth_date='1985-03-15',
user_external_id='user-005',
),
document=InlineDocument(content=pdf_base64, filename='contrato-consignado.pdf'),
return_url='https://app.example.com/done',
locale='pt-BR',
expires_in_minutes=60,
))
print('Session ID:', session.session_id)
print('Client Secret:', session.client_secret)
result = client.signing_sessions.wait_for_completion(session.session_id)
print('Status:', result.status) # COMPLETED
print('Evidence ID:', result.evidence_id)
session, err := client.SigningSessions.Create(ctx, &signdocs.CreateSigningSessionRequest{
Purpose: signdocs.PurposeDocumentSignature,
Policy: signdocs.Policy{Profile: "BIOMETRIC_SERPRO_AUTO_FALLBACK"},
Signer: signdocs.Signer{
Name: "Roberto Santos", CPF: "12345678901",
BirthDate: "1985-03-15", UserExternalID: "user-005",
},
Document: &signdocs.DocumentInline{Content: pdfBase64, Filename: "contrato-consignado.pdf"},
ReturnURL: "https://app.example.com/done",
Locale: "pt-BR",
ExpiresInMinutes: 60,
})
if err != nil { log.Fatal(err) }
fmt.Println("Session ID:", session.SessionID)
fmt.Println("Client Secret:", session.ClientSecret)
result, err := client.SigningSessions.WaitForCompletion(ctx, session.SessionID)
if err != nil { log.Fatal(err) }
fmt.Println("Status:", result.Status) // COMPLETED
fmt.Println("Evidence ID:", result.EvidenceID)
String pdfBase64 = Base64.getEncoder().encodeToString(
Files.readAllBytes(Path.of("contrato-consignado.pdf")));
CreateSigningSessionRequest request = new CreateSigningSessionRequest();
request.purpose = "DOCUMENT_SIGNATURE";
request.policy = new Policy("BIOMETRIC_SERPRO_AUTO_FALLBACK");
request.signer = new Signer("Roberto Santos", "user-005");
request.signer.cpf = "12345678901";
request.signer.birthDate = "1985-03-15";
request.document = new CreateSigningSessionRequest.InlineDocument(pdfBase64, "contrato-consignado.pdf");
request.returnUrl = "https://app.example.com/done";
request.locale = "pt-BR";
request.expiresInMinutes = 60;
SigningSession session = client.signingSessions().create(request);
System.out.println("Session ID: " + session.sessionId);
System.out.println("Client Secret: " + session.clientSecret);
SigningSessionStatusResponse result = client.signingSessions().waitForCompletion(session.sessionId);
System.out.println("Status: " + result.status); // COMPLETED
System.out.println("Evidence ID: " + result.evidenceId);
$pdfBase64 = base64_encode(file_get_contents('contrato-consignado.pdf'));
$session = $client->signingSessions->create(new CreateSigningSessionRequest(
purpose: 'DOCUMENT_SIGNATURE',
policy: new Policy(profile: 'BIOMETRIC_SERPRO_AUTO_FALLBACK'),
signer: new Signer(
name: 'Roberto Santos',
cpf: '12345678901',
birthDate: '1985-03-15',
userExternalId: 'user-005',
),
document: ['content' => $pdfBase64, 'filename' => 'contrato-consignado.pdf'],
returnUrl: 'https://app.example.com/done',
locale: 'pt-BR',
expiresInMinutes: 60,
));
echo "Session ID: " . $session->sessionId . "\n";
echo "Client Secret: " . $session->clientSecret . "\n";
$result = $client->signingSessions->waitForCompletion($session->sessionId);
echo "Status: " . $result->status . "\n"; // COMPLETED
echo "Evidence ID: " . $result->evidenceId . "\n";
var pdfBase64 = Convert.ToBase64String(
await File.ReadAllBytesAsync("contrato-consignado.pdf"));
var session = await client.SigningSessions.CreateAsync(new CreateSigningSessionRequest
{
Purpose = "DOCUMENT_SIGNATURE",
Policy = new Policy { Profile = "BIOMETRIC_SERPRO_AUTO_FALLBACK" },
Signer = new Signer
{
Name = "Roberto Santos",
Cpf = "12345678901",
BirthDate = "1985-03-15",
UserExternalId = "user-005",
},
Document = new InlineDocument { Content = pdfBase64, Filename = "contrato-consignado.pdf" },
ReturnUrl = "https://app.example.com/done",
Locale = "pt-BR",
ExpiresInMinutes = 60,
});
Console.WriteLine($"Session ID: {session.SessionId}");
Console.WriteLine($"Client Secret: {session.ClientSecret}");
var result = await client.SigningSessions.WaitForCompletionAsync(session.SessionId);
Console.WriteLine($"Status: {result.Status}"); // COMPLETED
Console.WriteLine($"Evidence ID: {result.EvidenceId}");
As etapas SERPRO são gerenciadas pela API de transações (/v1/transactions/{id}/steps/), não pelo endpoint advance. Para integração server-side completa, use a Transaction API diretamente.
// 1. Obter token OAuth2
const tokenRes = await fetch(`${baseUrl}/oauth2/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `grant_type=client_credentials&client_id=${clientId}&client_secret=${clientSecret}`,
});
const { access_token } = await tokenRes.json();
// 2. Criar transação com perfil BIOMETRIC_SERPRO_AUTO_FALLBACK
const txRes = await fetch(`${baseUrl}/v1/transactions`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${access_token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
purpose: 'DOCUMENT_SIGNATURE',
policy: { profile: 'BIOMETRIC_SERPRO_AUTO_FALLBACK' },
signer: { name: 'Roberto Santos', cpf: '12345678901', birthDate: '1985-03-15',
email: 'roberto@example.com', userExternalId: 'user-005' },
}),
});
const tx = await txRes.json();
const txId = tx.transactionId;
// 3. Upload do documento
await fetch(`${baseUrl}/v1/transactions/${txId}/document`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${access_token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ content: pdfBase64 }),
});
// 4. Listar etapas
const stepsRes = await fetch(`${baseUrl}/v1/transactions/${txId}/steps`,
{ headers: { 'Authorization': `Bearer ${access_token}` } });
const steps = await stepsRes.json();
// steps: PURPOSE_DISCLOSURE, BIOMETRIC_LIVENESS, BIOMETRIC_MATCH,
// SERPRO_IDENTITY_CHECK, DOCUMENT_PHOTO_MATCH (DORMANT)
// 5. Completar cada etapa em sequência (start → complete)
for (const step of steps.filter(s => s.status !== 'DORMANT')) {
await fetch(`${baseUrl}/v1/transactions/${txId}/steps/${step.stepId}/start`,
{ method: 'POST', headers: { 'Authorization': `Bearer ${access_token}` } });
// Payload depende do tipo de etapa:
// PURPOSE_DISCLOSURE: { acknowledged: true }
// BIOMETRIC_LIVENESS: (liveness session via hosted page)
// BIOMETRIC_MATCH: (automático após liveness)
// SERPRO_IDENTITY_CHECK: (automático — server-side)
await fetch(`${baseUrl}/v1/transactions/${txId}/steps/${step.stepId}/complete`,
{ method: 'POST', headers: { 'Authorization': `Bearer ${access_token}`,
'Content-Type': 'application/json' },
body: JSON.stringify(/* payload específico da etapa */) });
}
// 6. Se fallback ativou, DOCUMENT_PHOTO_MATCH agora é PENDING
// Completar com foto do documento + geolocalização
// 7. Finalizar transação
const finalizeRes = await fetch(`${baseUrl}/v1/transactions/${txId}/finalize`,
{ method: 'POST', headers: { 'Authorization': `Bearer ${access_token}` } });
const finalized = await finalizeRes.json();
console.log('Evidence ID:', finalized.evidenceId);
| CPF | Comportamento |
|---|---|
11111111111 |
biometricAvailable: false — dispara fallback |
12345678901 |
SERPRO sucesso — fluxo sem fallback |
Para etapas biométricas com biometricRequired ativo, geolocalização é obrigatória:
{
"geolocation": {
"latitude": -23.5505,
"longitude": -46.6333,
"accuracy": 10.0,
"source": "GPS"
}
}
Documentação completa NT65: nt65-biometria-consignado.md
Em vez de waitForCompletion (polling), registre um webhook para receber eventos em tempo real.
| Evento | Quando |
|---|---|
SIGNING_SESSION.COMPLETED |
Sessão concluída com sucesso |
SIGNING_SESSION.EXPIRED |
Sessão expirou |
SIGNING_SESSION.CANCELLED |
Sessão cancelada |
TRANSACTION.FALLBACK |
Auto-fallback ativado (NT65) |
{
"event": "SIGNING_SESSION.COMPLETED",
"payload": {
"sessionId": "ss_abc123",
"transactionId": "tx_def456",
"status": "COMPLETED",
"evidenceId": "ev_ghi789",
"completedAt": "2026-03-10T14:30:00Z"
},
"signature": "sha256=..."
}
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/webhooks/signdocs', express.raw({ type: 'application/json' }), (req, res) => {
// Verificar assinatura
const signature = req.headers['x-signdocs-signature'];
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
if (signature !== expected) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
switch (event.event) {
case 'SIGNING_SESSION.COMPLETED':
console.log('Sessão concluída:', event.payload.sessionId);
console.log('Evidence ID:', event.payload.evidenceId);
// Atualizar seu banco de dados
break;
case 'TRANSACTION.FALLBACK':
console.log('Fallback ativado:', event.payload.transactionId);
// Notificar equipe de compliance
break;
}
res.status(200).send('OK');
});
app.listen(3000);
import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.post('/webhooks/signdocs')
def handle_webhook():
signature = request.headers.get('X-SignDocs-Signature', '')
expected = 'sha256=' + hmac.new(
os.environ['WEBHOOK_SECRET'].encode(),
request.data,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(signature, expected):
return 'Invalid signature', 401
event = request.get_json()
if event['event'] == 'SIGNING_SESSION.COMPLETED':
print('Sessão concluída:', event['payload']['sessionId'])
print('Evidence ID:', event['payload']['evidenceId'])
elif event['event'] == 'TRANSACTION.FALLBACK':
print('Fallback ativado:', event['payload']['transactionId'])
return 'OK', 200
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("webhooks")]
public class WebhookController : ControllerBase
{
[HttpPost("signdocs")]
public async Task<IActionResult> HandleWebhook()
{
using var reader = new StreamReader(Request.Body);
var body = await reader.ReadToEndAsync();
var signature = Request.Headers["X-SignDocs-Signature"].ToString();
var secret = Environment.GetEnvironmentVariable("WEBHOOK_SECRET")!;
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var expected = "sha256=" + Convert.ToHexString(
hmac.ComputeHash(Encoding.UTF8.GetBytes(body))).ToLower();
if (signature != expected)
return Unauthorized("Invalid signature");
var doc = System.Text.Json.JsonDocument.Parse(body);
var eventType = doc.RootElement.GetProperty("event").GetString();
switch (eventType)
{
case "SIGNING_SESSION.COMPLETED":
var sessionId = doc.RootElement.GetProperty("payload")
.GetProperty("sessionId").GetString();
Console.WriteLine($"Sessão concluída: {sessionId}");
break;
case "TRANSACTION.FALLBACK":
var txId = doc.RootElement.GetProperty("payload")
.GetProperty("transactionId").GetString();
Console.WriteLine($"Fallback ativado: {txId}");
break;
}
return Ok("OK");
}
}
Guia completo de webhooks: webhooks-guide.md
| Erro | Causa | Solução |
|---|---|---|
409 Session is COMPLETED |
Tentou avançar uma sessão já concluída | Verifique o status antes de chamar advance |
401 Token expired |
clientSecret expirou (padrão: 60min) |
Crie nova sessão ou aumente expiresInMinutes |
422 Missing required field: signer.cpf |
Perfil biométrico sem CPF | Inclua signer.cpf (11 dígitos, sem formatação) |
422 Missing required field: signer.email |
Perfil OTP sem email | Inclua signer.email ou use otpChannel: 'sms' com signer.phone |
422 Missing required field: signer.birthDate |
Perfil SERPRO sem data de nascimento | Inclua signer.birthDate (YYYY-MM-DD) |
400 Document too large |
PDF > 10MB em base64 | Reduza o tamanho do PDF |
400 Profile requires feature flag |
Tenant sem feature flag habilitada | Contate suporte para habilitar o perfil |
| Popup bloqueado pelo navegador | sd.checkout() fora de evento de clique |
Vincule a chamada a um click handler |
409 Signature request expired |
Mais de 15min entre prepare_signing e complete_signing |
Chame prepare_signing novamente |
429 Rate limit exceeded |
Cota diária/mensal excedida | Verifique headers X-RateLimit-* na resposta |
Referência completa de erros: error-reference.md
Cria um envelope com modo paralelo, adiciona 2 signatários com perfil CLICK_ONLY e imprime as URLs de assinatura.
Backend: create envelope ──> envelopeId
│
add session 1 ──> url1, clientSecret1
add session 2 ──> url2, clientSecret2
│
Signatários assinam em paralelo ──> ENVELOPE.COMPLETED
│
Backend: poll ou webhook ──> combinedStamp
import { readFileSync } from 'fs';
const pdfBase64 = readFileSync('contrato.pdf').toString('base64');
// 1. Criar envelope
const envelope = await client.envelopes.create({
signingMode: 'PARALLEL',
totalSigners: 2,
document: { content: pdfBase64, filename: 'contrato.pdf' },
returnUrl: 'https://app.example.com/done',
locale: 'pt-BR',
expiresInMinutes: 1440,
});
console.log('Envelope ID:', envelope.envelopeId);
// 2. Adicionar signatário 1
const session1 = await client.envelopes.addSession(envelope.envelopeId, {
signer: { name: 'João Silva', email: 'joao@example.com', userExternalId: 'user-001' },
policy: { profile: 'CLICK_ONLY' },
purpose: 'DOCUMENT_SIGNATURE',
signerIndex: 1,
});
// 3. Adicionar signatário 2
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('URL Signatário 1:', session1.url);
console.log('URL Signatário 2:', session2.url);
import base64
from signdocs_brasil.models import CreateEnvelopeRequest, AddEnvelopeSessionRequest
with open('contrato.pdf', 'rb') as f:
pdf_base64 = base64.b64encode(f.read()).decode()
# 1. Criar envelope
envelope = client.envelopes.create(CreateEnvelopeRequest(
signing_mode='PARALLEL',
total_signers=2,
document_content=pdf_base64,
document_filename='contrato.pdf',
return_url='https://app.example.com/done',
locale='pt-BR',
expires_in_minutes=1440,
))
print('Envelope ID:', envelope.envelope_id)
# 2. Adicionar signatário 1
session1 = client.envelopes.add_session(envelope.envelope_id, AddEnvelopeSessionRequest(
signer_name='João Silva',
signer_email='joao@example.com',
signer_user_external_id='user-001',
policy_profile='CLICK_ONLY',
purpose='DOCUMENT_SIGNATURE',
signer_index=1,
))
# 3. Adicionar signatário 2
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('URL Signatário 1:', session1.url)
print('URL Signatário 2:', session2.url)
pdfBytes, _ := os.ReadFile("contrato.pdf")
pdfBase64 := base64.StdEncoding.EncodeToString(pdfBytes)
// 1. Criar envelope
envelope, err := client.Envelopes.Create(ctx, &signdocs.CreateEnvelopeRequest{
SigningMode: signdocs.SigningModeParallel,
TotalSigners: 2,
DocumentContent: pdfBase64,
DocumentFilename: "contrato.pdf",
ReturnURL: "https://app.example.com/done",
Locale: "pt-BR",
ExpiresInMinutes: 1440,
})
if err != nil { log.Fatal(err) }
fmt.Println("Envelope ID:", envelope.EnvelopeID)
// 2. Adicionar signatário 1
session1, err := client.Envelopes.AddSession(ctx, envelope.EnvelopeID, &signdocs.AddEnvelopeSessionRequest{
SignerName: "João Silva",
SignerEmail: "joao@example.com",
SignerUserExternalID: "user-001",
PolicyProfile: signdocs.PolicyProfileClickOnly,
Purpose: signdocs.PurposeDocumentSignature,
SignerIndex: 1,
})
if err != nil { log.Fatal(err) }
// 3. Adicionar signatário 2
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("URL Signatário 1:", session1.URL)
fmt.Println("URL Signatário 2:", session2.URL)
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
String pdfBase64 = Base64.getEncoder().encodeToString(Files.readAllBytes(Path.of("contrato.pdf")));
// 1. Criar envelope
CreateEnvelopeRequest envReq = new CreateEnvelopeRequest();
envReq.signingMode = "PARALLEL";
envReq.totalSigners = 2;
envReq.document = new CreateEnvelopeRequest.Document(pdfBase64, "contrato.pdf");
envReq.returnUrl = "https://app.example.com/done";
envReq.locale = "pt-BR";
envReq.expiresInMinutes = 1440;
Envelope envelope = client.envelopes().create(envReq);
System.out.println("Envelope ID: " + envelope.envelopeId);
// 2. Adicionar signatário 1
AddEnvelopeSessionRequest req1 = new AddEnvelopeSessionRequest();
req1.signer = new AddEnvelopeSessionRequest.Signer("João 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);
// 3. Adicionar signatário 2
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("URL Signatário 1: " + session1.url);
System.out.println("URL Signatário 2: " + session2.url);
$pdfBase64 = base64_encode(file_get_contents('contrato.pdf'));
// 1. Criar envelope
$envelope = $client->envelopes->create(new CreateEnvelopeRequest(
signingMode: 'PARALLEL',
totalSigners: 2,
documentContent: $pdfBase64,
documentFilename: 'contrato.pdf',
returnUrl: 'https://app.example.com/done',
locale: 'pt-BR',
expiresInMinutes: 1440,
));
echo "Envelope ID: " . $envelope->envelopeId . "\n";
// 2. Adicionar signatário 1
$session1 = $client->envelopes->addSession($envelope->envelopeId, new AddEnvelopeSessionRequest(
signerName: 'João Silva',
signerEmail: 'joao@example.com',
signerUserExternalId: 'user-001',
policyProfile: 'CLICK_ONLY',
purpose: 'DOCUMENT_SIGNATURE',
signerIndex: 1,
));
// 3. Adicionar signatário 2
$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 "URL Signatário 1: " . $session1->url . "\n";
echo "URL Signatário 2: " . $session2->url . "\n";
var pdfBase64 = Convert.ToBase64String(File.ReadAllBytes("contrato.pdf"));
// 1. Criar envelope
var envelope = await client.Envelopes.CreateAsync(new CreateEnvelopeRequest
{
SigningMode = "PARALLEL",
TotalSigners = 2,
Document = new EnvelopeDocument
{
Content = pdfBase64,
Filename = "contrato.pdf",
},
ReturnUrl = "https://app.example.com/done",
Locale = "pt-BR",
ExpiresInMinutes = 1440,
});
Console.WriteLine($"Envelope ID: {envelope.EnvelopeId}");
// 2. Adicionar signatário 1
var session1 = await client.Envelopes.AddSessionAsync(envelope.EnvelopeId, new AddEnvelopeSessionRequest
{
Signer = new Signer
{
UserExternalId = "user-001",
Name = "João Silva",
Email = "joao@example.com",
},
Policy = new Policy { Profile = "CLICK_ONLY" },
SignerIndex = 1,
});
// 3. Adicionar signatário 2
var session2 = await client.Envelopes.AddSessionAsync(envelope.EnvelopeId, new AddEnvelopeSessionRequest
{
Signer = new Signer
{
UserExternalId = "user-002",
Name = "Maria Souza",
Email = "maria@example.com",
},
Policy = new Policy { Profile = "CLICK_ONLY" },
SignerIndex = 2,
});
Console.WriteLine($"URL Signatário 1: {session1.Url}");
Console.WriteLine($"URL Signatário 2: {session2.Url}");
Cria um envelope com modo sequencial e 3 signatários com verificação biométrica. Cada signatário só pode assinar após o anterior concluir.
Backend: create envelope (SEQUENTIAL) ──> envelopeId
│
add session 1 (signerIndex: 1) ──> url1
add session 2 (signerIndex: 2) ──> url2 (aguarda session 1)
add session 3 (signerIndex: 3) ──> url3 (aguarda session 2)
│
Signatário 1 assina ──> Signatário 2 assina ──> Signatário 3 assina
│
ENVELOPE.COMPLETED
Obrigatório:
signer.cpfpara perfil BIOMETRIC.
import { readFileSync } from 'fs';
const pdfBase64 = readFileSync('contrato.pdf').toString('base64');
// 1. Criar envelope sequencial
const envelope = await client.envelopes.create({
signingMode: 'SEQUENTIAL',
totalSigners: 3,
document: { content: pdfBase64, filename: 'contrato.pdf' },
returnUrl: 'https://app.example.com/done',
locale: 'pt-BR',
expiresInMinutes: 4320,
});
console.log('Envelope ID:', envelope.envelopeId);
// 2. Adicionar signatário 1 — assina primeiro
const session1 = await client.envelopes.addSession(envelope.envelopeId, {
signer: { name: 'João Silva', cpf: '12345678900', email: 'joao@example.com', userExternalId: 'user-001' },
policy: { profile: 'BIOMETRIC' },
purpose: 'DOCUMENT_SIGNATURE',
signerIndex: 1,
});
// 3. Adicionar signatário 2 — assina após signatário 1
const session2 = await client.envelopes.addSession(envelope.envelopeId, {
signer: { name: 'Maria Souza', cpf: '98765432100', email: 'maria@example.com', userExternalId: 'user-002' },
policy: { profile: 'BIOMETRIC' },
purpose: 'DOCUMENT_SIGNATURE',
signerIndex: 2,
});
// 4. Adicionar signatário 3 — assina por último
const session3 = await client.envelopes.addSession(envelope.envelopeId, {
signer: { name: 'Carlos Lima', cpf: '11122233344', email: 'carlos@example.com', userExternalId: 'user-003' },
policy: { profile: 'BIOMETRIC' },
purpose: 'DOCUMENT_SIGNATURE',
signerIndex: 3,
});
console.log('URL Signatário 1:', session1.url);
console.log('URL Signatário 2:', session2.url);
console.log('URL Signatário 3:', session3.url);
import base64
from signdocs_brasil.models import CreateEnvelopeRequest, AddEnvelopeSessionRequest
with open('contrato.pdf', 'rb') as f:
pdf_base64 = base64.b64encode(f.read()).decode()
# 1. Criar envelope sequencial
envelope = client.envelopes.create(CreateEnvelopeRequest(
signing_mode='SEQUENTIAL',
total_signers=3,
document_content=pdf_base64,
document_filename='contrato.pdf',
return_url='https://app.example.com/done',
locale='pt-BR',
expires_in_minutes=4320,
))
print('Envelope ID:', envelope.envelope_id)
# 2. Adicionar signatário 1 — assina primeiro
session1 = client.envelopes.add_session(envelope.envelope_id, AddEnvelopeSessionRequest(
signer_name='João Silva',
signer_cpf='12345678900',
signer_email='joao@example.com',
signer_user_external_id='user-001',
policy_profile='BIOMETRIC',
purpose='DOCUMENT_SIGNATURE',
signer_index=1,
))
# 3. Adicionar signatário 2 — assina após signatário 1
session2 = client.envelopes.add_session(envelope.envelope_id, AddEnvelopeSessionRequest(
signer_name='Maria Souza',
signer_cpf='98765432100',
signer_email='maria@example.com',
signer_user_external_id='user-002',
policy_profile='BIOMETRIC',
purpose='DOCUMENT_SIGNATURE',
signer_index=2,
))
# 4. Adicionar signatário 3 — assina por último
session3 = client.envelopes.add_session(envelope.envelope_id, AddEnvelopeSessionRequest(
signer_name='Carlos Lima',
signer_cpf='11122233344',
signer_email='carlos@example.com',
signer_user_external_id='user-003',
policy_profile='BIOMETRIC',
purpose='DOCUMENT_SIGNATURE',
signer_index=3,
))
print('URL Signatário 1:', session1.url)
print('URL Signatário 2:', session2.url)
print('URL Signatário 3:', session3.url)
pdfBytes, _ := os.ReadFile("contrato.pdf")
pdfBase64 := base64.StdEncoding.EncodeToString(pdfBytes)
// 1. Criar envelope sequencial
envelope, err := client.Envelopes.Create(ctx, &signdocs.CreateEnvelopeRequest{
SigningMode: signdocs.SigningModeSequential,
TotalSigners: 3,
DocumentContent: pdfBase64,
DocumentFilename: "contrato.pdf",
ReturnURL: "https://app.example.com/done",
Locale: "pt-BR",
ExpiresInMinutes: 4320,
})
if err != nil { log.Fatal(err) }
fmt.Println("Envelope ID:", envelope.EnvelopeID)
// 2. Adicionar signatário 1 — assina primeiro
session1, err := client.Envelopes.AddSession(ctx, envelope.EnvelopeID, &signdocs.AddEnvelopeSessionRequest{
SignerName: "João Silva",
SignerCPF: "12345678900",
SignerEmail: "joao@example.com",
SignerUserExternalID: "user-001",
PolicyProfile: signdocs.PolicyProfileBiometric,
Purpose: signdocs.PurposeDocumentSignature,
SignerIndex: 1,
})
if err != nil { log.Fatal(err) }
// 3. Adicionar signatário 2 — assina após signatário 1
session2, err := client.Envelopes.AddSession(ctx, envelope.EnvelopeID, &signdocs.AddEnvelopeSessionRequest{
SignerName: "Maria Souza",
SignerCPF: "98765432100",
SignerEmail: "maria@example.com",
SignerUserExternalID: "user-002",
PolicyProfile: signdocs.PolicyProfileBiometric,
Purpose: signdocs.PurposeDocumentSignature,
SignerIndex: 2,
})
if err != nil { log.Fatal(err) }
// 4. Adicionar signatário 3 — assina por último
session3, err := client.Envelopes.AddSession(ctx, envelope.EnvelopeID, &signdocs.AddEnvelopeSessionRequest{
SignerName: "Carlos Lima",
SignerCPF: "11122233344",
SignerEmail: "carlos@example.com",
SignerUserExternalID: "user-003",
PolicyProfile: signdocs.PolicyProfileBiometric,
Purpose: signdocs.PurposeDocumentSignature,
SignerIndex: 3,
})
if err != nil { log.Fatal(err) }
fmt.Println("URL Signatário 1:", session1.URL)
fmt.Println("URL Signatário 2:", session2.URL)
fmt.Println("URL Signatário 3:", session3.URL)
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
String pdfBase64 = Base64.getEncoder().encodeToString(Files.readAllBytes(Path.of("contrato.pdf")));
// 1. Criar envelope sequencial
CreateEnvelopeRequest envReq = new CreateEnvelopeRequest();
envReq.signingMode = "SEQUENTIAL";
envReq.totalSigners = 3;
envReq.document = new CreateEnvelopeRequest.Document(pdfBase64, "contrato.pdf");
envReq.returnUrl = "https://app.example.com/done";
envReq.locale = "pt-BR";
envReq.expiresInMinutes = 4320;
Envelope envelope = client.envelopes().create(envReq);
System.out.println("Envelope ID: " + envelope.envelopeId);
// 2. Adicionar signatário 1 — assina primeiro
AddEnvelopeSessionRequest req1 = new AddEnvelopeSessionRequest();
req1.signer = new AddEnvelopeSessionRequest.Signer("João Silva", "joao@example.com", "user-001");
req1.signer.cpf = "12345678900";
req1.policy = new Policy("BIOMETRIC");
req1.purpose = "DOCUMENT_SIGNATURE";
req1.signerIndex = 1;
EnvelopeSession session1 = client.envelopes().addSession(envelope.envelopeId, req1);
// 3. Adicionar signatário 2 — assina após signatário 1
AddEnvelopeSessionRequest req2 = new AddEnvelopeSessionRequest();
req2.signer = new AddEnvelopeSessionRequest.Signer("Maria Souza", "maria@example.com", "user-002");
req2.signer.cpf = "98765432100";
req2.policy = new Policy("BIOMETRIC");
req2.purpose = "DOCUMENT_SIGNATURE";
req2.signerIndex = 2;
EnvelopeSession session2 = client.envelopes().addSession(envelope.envelopeId, req2);
// 4. Adicionar signatário 3 — assina por último
AddEnvelopeSessionRequest req3 = new AddEnvelopeSessionRequest();
req3.signer = new AddEnvelopeSessionRequest.Signer("Carlos Lima", "carlos@example.com", "user-003");
req3.signer.cpf = "11122233344";
req3.policy = new Policy("BIOMETRIC");
req3.purpose = "DOCUMENT_SIGNATURE";
req3.signerIndex = 3;
EnvelopeSession session3 = client.envelopes().addSession(envelope.envelopeId, req3);
System.out.println("URL Signatário 1: " + session1.url);
System.out.println("URL Signatário 2: " + session2.url);
System.out.println("URL Signatário 3: " + session3.url);
$pdfBase64 = base64_encode(file_get_contents('contrato.pdf'));
// 1. Criar envelope sequencial
$envelope = $client->envelopes->create(new CreateEnvelopeRequest(
signingMode: 'SEQUENTIAL',
totalSigners: 3,
documentContent: $pdfBase64,
documentFilename: 'contrato.pdf',
returnUrl: 'https://app.example.com/done',
locale: 'pt-BR',
expiresInMinutes: 4320,
));
echo "Envelope ID: " . $envelope->envelopeId . "\n";
// 2. Adicionar signatário 1 — assina primeiro
$session1 = $client->envelopes->addSession($envelope->envelopeId, new AddEnvelopeSessionRequest(
signerName: 'João Silva',
signerCpf: '12345678900',
signerEmail: 'joao@example.com',
signerUserExternalId: 'user-001',
policyProfile: 'BIOMETRIC',
purpose: 'DOCUMENT_SIGNATURE',
signerIndex: 1,
));
// 3. Adicionar signatário 2 — assina após signatário 1
$session2 = $client->envelopes->addSession($envelope->envelopeId, new AddEnvelopeSessionRequest(
signerName: 'Maria Souza',
signerCpf: '98765432100',
signerEmail: 'maria@example.com',
signerUserExternalId: 'user-002',
policyProfile: 'BIOMETRIC',
purpose: 'DOCUMENT_SIGNATURE',
signerIndex: 2,
));
// 4. Adicionar signatário 3 — assina por último
$session3 = $client->envelopes->addSession($envelope->envelopeId, new AddEnvelopeSessionRequest(
signerName: 'Carlos Lima',
signerCpf: '11122233344',
signerEmail: 'carlos@example.com',
signerUserExternalId: 'user-003',
policyProfile: 'BIOMETRIC',
purpose: 'DOCUMENT_SIGNATURE',
signerIndex: 3,
));
echo "URL Signatário 1: " . $session1->url . "\n";
echo "URL Signatário 2: " . $session2->url . "\n";
echo "URL Signatário 3: " . $session3->url . "\n";
var pdfBase64 = Convert.ToBase64String(File.ReadAllBytes("contrato.pdf"));
// 1. Criar envelope sequencial
var envelope = await client.Envelopes.CreateAsync(new CreateEnvelopeRequest
{
SigningMode = "SEQUENTIAL",
TotalSigners = 3,
Document = new EnvelopeDocument
{
Content = pdfBase64,
Filename = "contrato.pdf",
},
ReturnUrl = "https://app.example.com/done",
Locale = "pt-BR",
ExpiresInMinutes = 4320,
});
Console.WriteLine($"Envelope ID: {envelope.EnvelopeId}");
// 2. Adicionar signatário 1 — assina primeiro
var session1 = await client.Envelopes.AddSessionAsync(envelope.EnvelopeId, new AddEnvelopeSessionRequest
{
Signer = new Signer
{
UserExternalId = "user-001",
Name = "João Silva",
Cpf = "12345678900",
Email = "joao@example.com",
},
Policy = new Policy { Profile = "BIOMETRIC" },
SignerIndex = 1,
});
// 3. Adicionar signatário 2 — assina após signatário 1
var session2 = await client.Envelopes.AddSessionAsync(envelope.EnvelopeId, new AddEnvelopeSessionRequest
{
Signer = new Signer
{
UserExternalId = "user-002",
Name = "Maria Souza",
Cpf = "98765432100",
Email = "maria@example.com",
},
Policy = new Policy { Profile = "BIOMETRIC" },
SignerIndex = 2,
});
// 4. Adicionar signatário 3 — assina por último
var session3 = await client.Envelopes.AddSessionAsync(envelope.EnvelopeId, new AddEnvelopeSessionRequest
{
Signer = new Signer
{
UserExternalId = "user-003",
Name = "Carlos Lima",
Cpf = "11122233344",
Email = "carlos@example.com",
},
Policy = new Policy { Profile = "BIOMETRIC" },
SignerIndex = 3,
});
Console.WriteLine($"URL Signatário 1: {session1.Url}");
Console.WriteLine($"URL Signatário 2: {session2.Url}");
Console.WriteLine($"URL Signatário 3: {session3.Url}");
Consulta o status de um envelope via polling e, quando todos os signatários concluírem, baixa o PDF com o carimbo combinado.
Backend: poll envelope status ──> completedSessions / totalSigners
│
(loop até COMPLETED) │
│
Backend: combined stamp ──> downloadUrl
│
Backend: download PDF ──> contrato-assinado-completo.pdf
import { writeFileSync } from 'fs';
const envelopeId = 'env_01J5X7K9M2N8P4Q6R3S1T0'; // ID do envelope criado anteriormente
// 1. Polling até conclusão
let detail = await client.envelopes.get(envelopeId);
while (detail.status !== 'COMPLETED') {
console.log(`Progresso: ${detail.completedSessions}/${detail.totalSigners}`);
for (const s of detail.sessions) {
console.log(` ${s.signerName} (${s.signerIndex}): ${s.status}`);
}
await new Promise(r => setTimeout(r, 5000)); // aguarda 5s
detail = await client.envelopes.get(envelopeId);
}
console.log('Envelope concluído!');
// 2. Baixar carimbo combinado
const stamp = await client.envelopes.combinedStamp(envelopeId);
console.log('Download URL:', stamp.downloadUrl);
console.log('Total de assinaturas:', stamp.signerCount);
const res = await fetch(stamp.downloadUrl);
const pdf = Buffer.from(await res.arrayBuffer());
writeFileSync('contrato-assinado-completo.pdf', pdf);
console.log('PDF salvo: contrato-assinado-completo.pdf');
import time
from pathlib import Path
import httpx
envelope_id = 'env_01J5X7K9M2N8P4Q6R3S1T0' # ID do envelope criado anteriormente
# 1. Polling até conclusão
detail = client.envelopes.get(envelope_id)
while detail.status != 'COMPLETED':
print(f'Progresso: {detail.completed_sessions}/{detail.total_signers}')
for s in detail.sessions:
print(f' {s.signer_name} ({s.signer_index}): {s.status}')
time.sleep(5) # aguarda 5s
detail = client.envelopes.get(envelope_id)
print('Envelope concluído!')
# 2. Baixar carimbo combinado
stamp = client.envelopes.combined_stamp(envelope_id)
print('Download URL:', stamp.download_url)
print('Total de assinaturas:', stamp.signer_count)
pdf = httpx.get(stamp.download_url).content
Path('contrato-assinado-completo.pdf').write_bytes(pdf)
print('PDF salvo: contrato-assinado-completo.pdf')
envelopeID := "env_01J5X7K9M2N8P4Q6R3S1T0" // ID do envelope criado anteriormente
// 1. Polling até conclusão
detail, err := client.Envelopes.Get(ctx, envelopeID)
if err != nil { log.Fatal(err) }
for detail.Status != "COMPLETED" {
fmt.Printf("Progresso: %d/%d\n", detail.CompletedSessions, detail.TotalSigners)
for _, s := range detail.Sessions {
fmt.Printf(" %s (%d): %s\n", s.SignerName, s.SignerIndex, s.Status)
}
time.Sleep(5 * time.Second) // aguarda 5s
detail, err = client.Envelopes.Get(ctx, envelopeID)
if err != nil { log.Fatal(err) }
}
fmt.Println("Envelope concluído!")
// 2. Baixar carimbo combinado
stamp, err := client.Envelopes.CombinedStamp(ctx, envelopeID)
if err != nil { log.Fatal(err) }
fmt.Println("Download URL:", stamp.DownloadURL)
fmt.Println("Total de assinaturas:", stamp.SignerCount)
resp, _ := http.Get(stamp.DownloadURL)
defer resp.Body.Close()
pdf, _ := io.ReadAll(resp.Body)
os.WriteFile("contrato-assinado-completo.pdf", pdf, 0644)
fmt.Println("PDF salvo: contrato-assinado-completo.pdf")
String envelopeId = "env_01J5X7K9M2N8P4Q6R3S1T0"; // ID do envelope criado anteriormente
// 1. Polling até conclusão
EnvelopeDetail detail = client.envelopes().get(envelopeId);
while (!"COMPLETED".equals(detail.status)) {
System.out.println("Progresso: " + detail.completedSessions + "/" + detail.totalSigners);
for (EnvelopeSessionSummary s : detail.sessions) {
System.out.println(" " + s.signerName + " (" + s.signerIndex + "): " + s.status);
}
Thread.sleep(5000); // aguarda 5s
detail = client.envelopes().get(envelopeId);
}
System.out.println("Envelope concluído!");
// 2. Baixar carimbo combinado
CombinedStampResponse stamp = client.envelopes().combinedStamp(envelopeId);
System.out.println("Download URL: " + stamp.downloadUrl);
System.out.println("Total de assinaturas: " + stamp.signerCount);
byte[] pdf = new URL(stamp.downloadUrl).openStream().readAllBytes();
Files.write(Path.of("contrato-assinado-completo.pdf"), pdf);
System.out.println("PDF salvo: contrato-assinado-completo.pdf");
$envelopeId = 'env_01J5X7K9M2N8P4Q6R3S1T0'; // ID do envelope criado anteriormente
// 1. Polling até conclusão
$detail = $client->envelopes->get($envelopeId);
while ($detail->status !== 'COMPLETED') {
echo "Progresso: " . $detail->completedSessions . "/" . $detail->totalSigners . "\n";
foreach ($detail->sessions as $s) {
echo " " . $s->signerName . " (" . $s->signerIndex . "): " . $s->status . "\n";
}
sleep(5); // aguarda 5s
$detail = $client->envelopes->get($envelopeId);
}
echo "Envelope concluído!\n";
// 2. Baixar carimbo combinado
$stamp = $client->envelopes->combinedStamp($envelopeId);
echo "Download URL: " . $stamp->downloadUrl . "\n";
echo "Total de assinaturas: " . $stamp->signerCount . "\n";
$pdf = file_get_contents($stamp->downloadUrl);
file_put_contents('contrato-assinado-completo.pdf', $pdf);
echo "PDF salvo: contrato-assinado-completo.pdf\n";
var envelopeId = "env_01J5X7K9M2N8P4Q6R3S1T0"; // ID do envelope criado anteriormente
// 1. Polling até conclusão
var detail = await client.Envelopes.GetAsync(envelopeId);
while (detail.Status != "COMPLETED")
{
Console.WriteLine($"Progresso: {detail.CompletedSessions}/{detail.TotalSigners}");
foreach (var s in detail.Sessions)
{
Console.WriteLine($" {s.SignerName} ({s.SignerIndex}): {s.Status}");
}
await Task.Delay(5000); // aguarda 5s
detail = await client.Envelopes.GetAsync(envelopeId);
}
Console.WriteLine("Envelope concluído!");
// 2. Baixar carimbo combinado
var stamp = await client.Envelopes.CombinedStampAsync(envelopeId);
Console.WriteLine($"Download URL: {stamp.DownloadUrl}");
Console.WriteLine($"Total de assinaturas: {stamp.SignerCount}");
var pdf = await new HttpClient().GetByteArrayAsync(stamp.DownloadUrl);
File.WriteAllBytes("contrato-assinado-completo.pdf", pdf);
Console.WriteLine("PDF salvo: contrato-assinado-completo.pdf");