Guias de Desenvolvimento

Signing Sessions

Guias dos SDKs

Guia de Início Rápido — SignDocs Brasil External API

Complete o fluxo de assinatura eletrônica de ponta a ponta em aproximadamente 5 minutos.

Sumário

  1. Pré-requisitos
  2. Obter token de acesso
  3. Criar transação
  4. Upload de documento
  5. Listar e iniciar etapas
  6. Concluir etapa
  7. Finalizar transação
  8. Verificar evidência
  9. Próximos passos

Convenções

Item Valor
Base URL (HML)https://api-hml.signdocs.com.br
AutenticaçãoOAuth2 client_credentials — Bearer JWT
Content-Type (API)application/json
Content-Type (token)application/x-www-form-urlencoded
Formato de errosRFC 7807 application/problem+json
TimestampsUTC ISO 8601

1. Pré-requisitos

Antes de começar, você precisa de:

Os exemplos abaixo usam o ambiente de homologação (HML). Substitua a base URL por https://api.signdocs.com.br quando estiver pronto para produção.

Exporte as credenciais como variáveis de ambiente para facilitar a execução dos exemplos:

export BASE_URL="https://api-hml.signdocs.com.br"
export CLIENT_ID="seu_client_id"
export CLIENT_SECRET="seu_client_secret"

2. Obter token de acesso

Solicite um token JWT via OAuth2 client_credentials. O token expira em 900 segundos (15 minutos).

Escopos disponíveis: transactions:read transactions:write steps:write evidence:read webhooks:write

cURL

TOKEN=$(curl -s -X POST "$BASE_URL/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials\
&client_id=$CLIENT_ID\
&client_secret=$CLIENT_SECRET\
&scope=transactions:read transactions:write steps:write evidence:read" \
  | jq -r '.access_token')

echo "$TOKEN"

Node.js (fetch)

const BASE_URL = process.env.BASE_URL;

const tokenRes = await fetch(`${BASE_URL}/oauth2/token`, {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "client_credentials",
    client_id: process.env.CLIENT_ID,
    client_secret: process.env.CLIENT_SECRET,
    scope: "transactions:read transactions:write steps:write evidence:read",
  }),
});

const { access_token, token_type, expires_in, scope } = await tokenRes.json();
console.log("Token obtido, expira em", expires_in, "segundos");

Python (requests)

import os, requests

BASE_URL = os.environ["BASE_URL"]

token_resp = requests.post(f"{BASE_URL}/oauth2/token", data={
    "grant_type": "client_credentials",
    "client_id": os.environ["CLIENT_ID"],
    "client_secret": os.environ["CLIENT_SECRET"],
    "scope": "transactions:read transactions:write steps:write evidence:read",
})
token_resp.raise_for_status()
access_token = token_resp.json()["access_token"]
print("Token obtido com sucesso")

Resposta (200):

{
  "access_token": "eyJhbGciOiJFUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "transactions:read transactions:write steps:write evidence:read"
}

3. Criar transação

Crie uma transação com o perfil de política CLICK_ONLY (o mais simples — apenas aceite por clique). A API retorna a transação com as etapas resolvidas automaticamente pelo policy engine.

Perfis de política disponíveis:

Perfil Etapas geradas
CLICK_ONLYCLICK_ACCEPT
CLICK_PLUS_OTPCLICK_ACCEPT, OTP_CHALLENGE
BIOMETRICBIOMETRIC_LIVENESS
BIOMETRIC_PLUS_OTPBIOMETRIC_LIVENESS, OTP_CHALLENGE
DIGITAL_CERTIFICATECLICK_ACCEPT, DIGITAL_SIGN_A1
CUSTOMConfiguração personalizada

Campos do signer:

Campo Obrigatório Descrição
nameSimNome do assinante
userExternalIdSimIdentificador único no seu sistema
emailNão*Obrigatório para perfis com OTP
displayNameNãoNome de exibição (usa name se omitido)
cpfCondicional†CPF do assinante (11 dígitos, sem pontuação)
cnpjCondicional†CNPJ do assinante (14 dígitos, sem pontuação)

† É obrigatório informar pelo menos um entre cpf e cnpj.

cURL

TX=$(curl -s -X POST "$BASE_URL/v1/transactions" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: quickstart-$(date +%s)" \
  -d '{
    "purpose": "DOCUMENT_SIGNATURE",
    "policy": { "profile": "CLICK_ONLY" },
    "signer": {
      "name": "Maria Silva",
      "userExternalId": "usr_12345",
      "cpf": "12345678901"
    }
  }')

TX_ID=$(echo "$TX" | jq -r '.transactionId')
echo "Transação criada: $TX_ID"
echo "$TX" | jq .

Node.js (fetch)

const headers = {
  Authorization: `Bearer ${access_token}`,
  "Content-Type": "application/json",
  "X-Idempotency-Key": `quickstart-${Date.now()}`,
};

const txRes = await fetch(`${BASE_URL}/v1/transactions`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    purpose: "DOCUMENT_SIGNATURE",
    policy: { profile: "CLICK_ONLY" },
    signer: {
      name: "Maria Silva",
      userExternalId: "usr_12345",
      cpf: "12345678901",
    },
  }),
});

const tx = await txRes.json();
const txId = tx.transactionId;
console.log("Transação criada:", txId);
console.log("Etapas:", tx.steps);

Python (requests)

import time

headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type": "application/json",
    "X-Idempotency-Key": f"quickstart-{int(time.time())}",
}

tx_resp = requests.post(f"{BASE_URL}/v1/transactions", headers=headers, json={
    "purpose": "DOCUMENT_SIGNATURE",
    "policy": {"profile": "CLICK_ONLY"},
    "signer": {
        "name": "Maria Silva",
        "userExternalId": "usr_12345",
        "cpf": "12345678901",
    },
})
tx_resp.raise_for_status()
tx = tx_resp.json()
tx_id = tx["transactionId"]
print(f"Transação criada: {tx_id}")
print(f"Etapas: {tx['steps']}")

Resposta (201):

{
  "transactionId": "01HWXYZ...",
  "status": "CREATED",
  "purpose": "DOCUMENT_SIGNATURE",
  "policy": { "profile": "CLICK_ONLY" },
  "steps": [
    {
      "stepId": "01HWXYZ...",
      "type": "CLICK_ACCEPT",
      "status": "PENDING",
      "order": 0
    }
  ],
  "userExternalId": "usr_12345",
  "createdAt": "2026-02-22T14:00:00.000Z",
  "expiresAt": "2026-02-23T14:00:00.000Z"
}

4. Upload de documento

Envie o PDF codificado em base64 no campo content. O documento será armazenado e um hash SHA-256 será calculado no servidor. Tamanho máximo: 10 MB.

Você também pode enviar o documento inline no momento da criação da transação usando o campo document.content. Neste guia, usamos o endpoint separado para clareza.

cURL

# Codifique o PDF em base64
PDF_B64=$(base64 -w0 contrato.pdf)

curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/document" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"content\": \"$PDF_B64\"}" | jq .

Node.js (fetch)

import { readFileSync } from "node:fs";

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

const docRes = await fetch(`${BASE_URL}/v1/transactions/${txId}/document`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${access_token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ content: pdfBase64 }),
});

const doc = await docRes.json();
console.log("Documento enviado. Hash:", doc.documentHash);

Python (requests)

import base64

with open("contrato.pdf", "rb") as f:
    pdf_b64 = base64.b64encode(f.read()).decode()

doc_resp = requests.post(
    f"{BASE_URL}/v1/transactions/{tx_id}/document",
    headers=headers,
    json={"content": pdf_b64},
)
doc_resp.raise_for_status()
doc = doc_resp.json()
print(f"Documento enviado. Hash: {doc['documentHash']}")

Resposta (200):

{
  "transactionId": "01HWXYZ...",
  "status": "DOCUMENT_UPLOADED",
  "documentHash": "sha256:a1b2c3d4e5f6..."
}

5. Listar e iniciar etapas

Primeiro, liste as etapas para obter os IDs. Depois, inicie a próxima etapa pendente. As etapas devem ser executadas em ordem — a API valida que etapas anteriores estejam concluídas antes de permitir o início da próxima.

5a. Listar etapas

cURL

curl -s -X GET "$BASE_URL/v1/transactions/$TX_ID/steps" \
  -H "Authorization: Bearer $TOKEN" | jq .

Node.js (fetch)

const stepsRes = await fetch(`${BASE_URL}/v1/transactions/${txId}/steps`, {
  headers: { Authorization: `Bearer ${access_token}` },
});

const { steps } = await stepsRes.json();
const stepId = steps[0].stepId;
console.log("Etapas:", steps);

Python (requests)

steps_resp = requests.get(
    f"{BASE_URL}/v1/transactions/{tx_id}/steps",
    headers=headers,
)
steps_resp.raise_for_status()
steps = steps_resp.json()["steps"]
step_id = steps[0]["stepId"]
print(f"Etapa a iniciar: {step_id} ({steps[0]['type']})")

Resposta (200):

{
  "steps": [
    {
      "stepId": "01HWXYZ...",
      "type": "CLICK_ACCEPT",
      "status": "PENDING",
      "order": 0,
      "attempts": 0,
      "maxAttempts": 10
    }
  ]
}

5b. Iniciar etapa

cURL

STEP_ID=$(echo "$TX" | jq -r '.steps[0].stepId')

curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$STEP_ID/start" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}' | jq .

Node.js (fetch)

const startRes = await fetch(
  `${BASE_URL}/v1/transactions/${txId}/steps/${stepId}/start`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${access_token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({}),
  },
);

const startData = await startRes.json();
console.log("Etapa iniciada:", startData);

Python (requests)

start_resp = requests.post(
    f"{BASE_URL}/v1/transactions/{tx_id}/steps/{step_id}/start",
    headers=headers,
    json={},
)
start_resp.raise_for_status()
print(f"Etapa iniciada: {start_resp.json()}")

Resposta (200):

{
  "stepId": "01HWXYZ...",
  "type": "CLICK_ACCEPT",
  "status": "STARTED"
}

6. Concluir etapa

Para etapas do tipo CLICK_ACCEPT, envie accepted: true no corpo da requisição. Cada tipo de etapa possui parâmetros diferentes:

Tipo de etapa Campos do corpo
CLICK_ACCEPT{ "accepted": true }
OTP_CHALLENGE{ "otpCode": "123456" }
BIOMETRIC_LIVENESS{} (validação automática via sessão)
DOCUMENT_PHOTO_MATCH{ "documentImage": "<base64>", "documentType": "CNH" }
SERPRO_IDENTITY_CHECK{ "cpf": "...", "name": "...", "birthDate": "..." }
Reenvio de OTP: Para reenviar o código OTP, basta chamar POST .../steps/{stepId}/start novamente na etapa OTP_CHALLENGE. A etapa OTP_VERIFY pareada será automaticamente resetada se estiver em status FAILED ou STARTED.

cURL

curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$STEP_ID/complete" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"accepted": true}' | jq .

Node.js (fetch)

const completeRes = await fetch(
  `${BASE_URL}/v1/transactions/${txId}/steps/${stepId}/complete`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${access_token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ accepted: true }),
  },
);

const completeData = await completeRes.json();
console.log("Etapa concluída:", completeData);

Python (requests)

complete_resp = requests.post(
    f"{BASE_URL}/v1/transactions/{tx_id}/steps/{step_id}/complete",
    headers=headers,
    json={"accepted": True},
)
complete_resp.raise_for_status()
print(f"Etapa concluída: {complete_resp.json()}")

Resposta (200):

{
  "stepId": "01HWXYZ...",
  "type": "CLICK_ACCEPT",
  "status": "COMPLETED",
  "completedAt": "2026-02-22T14:02:00.000Z"
}

7. Finalizar transação

Após todas as etapas estarem concluídas, finalize a transação. O servidor valida que todas as etapas foram completadas, gera o Evidence Pack (arquivo .p7m assinado com ECDSA via KMS), aplica o carimbo no PDF e retorna o evidenceId para verificação pública.

cURL

curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/finalize" \
  -H "Authorization: Bearer $TOKEN" | jq .

Node.js (fetch)

const finalizeRes = await fetch(
  `${BASE_URL}/v1/transactions/${txId}/finalize`,
  {
    method: "POST",
    headers: { Authorization: `Bearer ${access_token}` },
  },
);

const result = await finalizeRes.json();
console.log("Transação finalizada!");
console.log("Evidence ID:", result.evidenceId);
console.log("Evidence Hash:", result.evidenceHash);

Python (requests)

finalize_resp = requests.post(
    f"{BASE_URL}/v1/transactions/{tx_id}/finalize",
    headers=headers,
)
finalize_resp.raise_for_status()
result = finalize_resp.json()
evidence_id = result["evidenceId"]
print(f"Transação finalizada! Evidence ID: {evidence_id}")
print(f"Evidence Hash: {result['evidenceHash']}")

Resposta (200):

{
  "transactionId": "01HWXYZ...",
  "status": "COMPLETED",
  "evidenceId": "01HWABC...",
  "evidenceHash": "sha256:f7e8d9c0b1a2...",
  "completedAt": "2026-02-22T14:03:00.000Z"
}

8. Verificar evidência

O endpoint de verificação é público — não requer autenticação. Qualquer pessoa com o evidenceId pode validar a existência e integridade da assinatura. Nenhuma informação pessoal (PII) é exposta nesta consulta.

cURL

EVIDENCE_ID="01HWABC..."  # Use o evidenceId da etapa anterior

curl -s -X GET "$BASE_URL/v1/verify/$EVIDENCE_ID" | jq .

Node.js (fetch)

const evidenceId = result.evidenceId;

const verifyRes = await fetch(`${BASE_URL}/v1/verify/${evidenceId}`);
const verification = await verifyRes.json();
console.log("Verificação pública:");
console.log("  Status:", verification.status);
console.log("  Política:", verification.policy.profile);
console.log("  Etapas concluídas:", verification.steps.length);
console.log("  Evidence Hash:", verification.evidenceHash);

Python (requests)

# Sem header de Authorization — endpoint público
verify_resp = requests.get(f"{BASE_URL}/v1/verify/{evidence_id}")
verify_resp.raise_for_status()
verification = verify_resp.json()
print(f"Status: {verification['status']}")
print(f"Política: {verification['policy']['profile']}")
print(f"Etapas: {len(verification['steps'])}")
print(f"Evidence Hash: {verification['evidenceHash']}")

Resposta (200):

{
  "evidenceId": "01HWABC...",
  "status": "COMPLETED",
  "transactionId": "01HWXYZ...",
  "purpose": "DOCUMENT_SIGNATURE",
  "documentHash": "sha256:a1b2c3d4e5f6...",
  "evidenceHash": "sha256:f7e8d9c0b1a2...",
  "policy": { "profile": "CLICK_ONLY" },
  "steps": [
    {
      "type": "CLICK_ACCEPT",
      "status": "COMPLETED",
      "order": 0,
      "completedAt": "2026-02-22T14:02:00.000Z"
    }
  ],
  "signer": { "displayName": "Maria Silva" },
  "tenantName": "Sua Empresa Ltda",
  "createdAt": "2026-02-22T14:00:00.000Z",
  "completedAt": "2026-02-22T14:03:00.000Z"
}

9. Próximos passos

Agora que você completou o fluxo básico, explore os recursos avançados:

Webhooks

Receba notificações em tempo real sobre eventos da transação (TRANSACTION.CREATED, TRANSACTION.COMPLETED, etc.). Registre um endpoint:

curl -s -X POST "$BASE_URL/v1/webhooks" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://seu-servidor.com/webhooks/signdocs",
    "events": ["TRANSACTION.COMPLETED"]
  }'

Assinatura biométrica

Use o perfil BIOMETRIC ou BIOMETRIC_PLUS_OTP para adicionar verificação de liveness facial via Amazon Rekognition. Suporta modo HOSTED_PAGE (página hospedada pelo SignDocs Brasil) ou BANK_APP (integração direta via SDK).

Certificado digital ICP-Brasil

Use o perfil DIGITAL_CERTIFICATE para assinaturas com certificado A1. O protocolo de duas fases (prepare + complete) garante que a chave privada nunca saia da sua infraestrutura. Consulte o Guia de Assinatura Digital para detalhes.

Enrollment de usuários

Pré-cadastre usuários com foto de referência para comparação facial em transações futuras. O campo cpf é obrigatório:

PUT /v1/users/{userExternalId}/enrollment
Content-Type: application/json

{
  "image": "<base64 da imagem JPEG>",
  "cpf": "12345678901",
  "source": "BANK_PROVIDED"
}

Fontes disponíveis para source:

Upload via presigned URL

Para documentos maiores ou cenários onde o base64 inline não é ideal, use o fluxo de upload em duas etapas:

POST /v1/transactions/{id}/document/presign  ->  uploadUrl
PUT  {uploadUrl}                              ->  upload direto ao S3
POST /v1/transactions/{id}/document/confirm   ->  confirmação

Tratamento de erros

Todos os erros seguem o formato RFC 7807 Problem Details:

{
  "type": "https://api.signdocs.com.br/problems/conflict",
  "title": "Conflict",
  "status": 409,
  "detail": "Transaction is already finalized",
  "instance": "/v1/transactions/01HWXYZ.../finalize"
}

Códigos HTTP comuns:

Código Descrição
400Requisição inválida (campos obrigatórios ausentes, formato incorreto)
401Token ausente, expirado ou inválido
404Recurso não encontrado
409Conflito de estado (etapa já concluída, transação já finalizada)
422Erro de validação de negócio (política incompatível, email obrigatório para OTP)
429Limite de requisições excedido

Fluxo completo em um único script

Bash

#!/usr/bin/env bash
set -euo pipefail

BASE_URL="https://api-hml.signdocs.com.br"

# 1. Token
TOKEN=$(curl -sf -X POST "$BASE_URL/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&scope=transactions:read transactions:write steps:write evidence:read" \
  | jq -r '.access_token')

# 2. Criar transação
TX=$(curl -sf -X POST "$BASE_URL/v1/transactions" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: qs-$(date +%s)" \
  -d '{
    "purpose": "DOCUMENT_SIGNATURE",
    "policy": {"profile": "CLICK_ONLY"},
    "signer": {"name": "Maria Silva", "userExternalId": "usr_12345", "cpf": "12345678901"}
  }')
TX_ID=$(echo "$TX" | jq -r '.transactionId')
STEP_ID=$(echo "$TX" | jq -r '.steps[0].stepId')

# 3. Upload do documento
PDF_B64=$(base64 -w0 contrato.pdf)
curl -sf -X POST "$BASE_URL/v1/transactions/$TX_ID/document" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"content\": \"$PDF_B64\"}" > /dev/null

# 4. Iniciar etapa
curl -sf -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$STEP_ID/start" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}' > /dev/null

# 5. Concluir etapa
curl -sf -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$STEP_ID/complete" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"accepted": true}' > /dev/null

# 6. Finalizar
RESULT=$(curl -sf -X POST "$BASE_URL/v1/transactions/$TX_ID/finalize" \
  -H "Authorization: Bearer $TOKEN")

EVIDENCE_ID=$(echo "$RESULT" | jq -r '.evidenceId')
echo "Transação finalizada! Evidence ID: $EVIDENCE_ID"

# 7. Verificar (público)
curl -sf "$BASE_URL/v1/verify/$EVIDENCE_ID" | jq .

Python

#!/usr/bin/env python3
"""SignDocs Brasil External API — fluxo completo CLICK_ONLY."""

import base64, os, time, requests

BASE_URL = "https://api-hml.signdocs.com.br"
CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]

# 1. Token
token_resp = requests.post(f"{BASE_URL}/oauth2/token", data={
    "grant_type": "client_credentials",
    "client_id": CLIENT_ID,
    "client_secret": CLIENT_SECRET,
    "scope": "transactions:read transactions:write steps:write evidence:read",
})
token_resp.raise_for_status()
token = token_resp.json()["access_token"]
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}

# 2. Criar transação
tx_resp = requests.post(f"{BASE_URL}/v1/transactions", headers={
    **headers, "X-Idempotency-Key": f"qs-{int(time.time())}",
}, json={
    "purpose": "DOCUMENT_SIGNATURE",
    "policy": {"profile": "CLICK_ONLY"},
    "signer": {"name": "Maria Silva", "userExternalId": "usr_12345", "cpf": "12345678901"},
})
tx_resp.raise_for_status()
tx = tx_resp.json()
tx_id, step_id = tx["transactionId"], tx["steps"][0]["stepId"]

# 3. Upload do documento
with open("contrato.pdf", "rb") as f:
    pdf_b64 = base64.b64encode(f.read()).decode()
requests.post(f"{BASE_URL}/v1/transactions/{tx_id}/document",
              headers=headers, json={"content": pdf_b64}).raise_for_status()

# 4. Iniciar etapa
requests.post(f"{BASE_URL}/v1/transactions/{tx_id}/steps/{step_id}/start",
              headers=headers, json={}).raise_for_status()

# 5. Concluir etapa
requests.post(f"{BASE_URL}/v1/transactions/{tx_id}/steps/{step_id}/complete",
              headers=headers, json={"accepted": True}).raise_for_status()

# 6. Finalizar
result = requests.post(f"{BASE_URL}/v1/transactions/{tx_id}/finalize",
                       headers=headers)
result.raise_for_status()
evidence_id = result.json()["evidenceId"]
print(f"Transação finalizada! Evidence ID: {evidence_id}")

# 7. Verificar (público — sem autenticação)
verification = requests.get(f"{BASE_URL}/v1/verify/{evidence_id}")
verification.raise_for_status()
print(verification.json())