Primeira Chamada à API de Assinatura com cURL (REST puro)

Você não precisa de um SDK para colocar uma assinatura digital com validade jurídica no ar. A API SignDocs é REST puro sobre HTTPS com payloads JSON, o que significa que qualquer linguagem capaz de fazer uma requisição HTTP — Ruby, Elixir, Rust, Bash, PowerShell ou um simples cURL no terminal — consegue criar uma sessão de assinatura, enviar um documento e receber eventos de conclusão. Este guia mostra o fluxo completo, do token OAuth2 ao webhook validado, usando só cURL.

A proposta aqui é deliberadamente language-agnostic. Em vez de esconder o protocolo atrás de uma biblioteca, vamos expor cada requisição HTTP crua, com cabeçalhos, corpo e resposta reais. Depois que você entende a API neste nível, traduzir o fluxo para a sua linguagem é trivial — e você ganha a capacidade de depurar qualquer integração olhando diretamente para os bytes que trafegam.

Se você procura um caminho ainda mais alto-nível, temos SDKs oficiais e tutoriais dedicados — por exemplo, integração em Node.js e integração em Python. Mas se a sua stack não tem SDK oficial, ou se você quer entender a API por baixo dos panos, REST puro funciona em todo lugar. Este artigo é também um bom complemento ao guia conceitual de o que é uma API de assinatura digital.

Por que (e quando) usar REST puro em vez de um SDK

SDKs existem para economizar tempo: eles encapsulam autenticação, serialização JSON, retries e tipagem. A SignDocs mantém SDKs oficiais para TypeScript/Node, Python, Go, Java, PHP e C#/.NET. Para essas linguagens, usar o SDK é quase sempre a melhor escolha. Mas há cenários claros em que ir direto ao REST é o caminho certo:

  • Linguagem sem SDK oficial: Ruby, Elixir, Rust, Kotlin nativo, Dart, Bash/shell scripts. O REST cobre 100% das funcionalidades, então você nunca fica preso por falta de biblioteca.
  • Provas de conceito e exploração: uma chamada cURL no terminal é a forma mais rápida de validar credenciais e ver a forma exata de uma resposta.
  • Depuração: quando algo falha através de um SDK, reproduzir a requisição em cURL isola se o problema é seu código ou a API.
  • Ambientes restritos: funções serverless minimalistas, CI/CD pipelines, runners onde adicionar dependências é indesejável.
  • Aprendizado: entender o protocolo cru torna você independente de qualquer SDK específico.
Regra prática: use o SDK oficial quando ele existir para a sua linguagem; use REST puro quando não existir, quando estiver depurando, ou quando quiser entender exatamente o que está acontecendo na camada HTTP. O contrato da API é idêntico nos dois caminhos — um SDK é apenas um wrapper sobre os mesmos endpoints que você verá abaixo.

O que você vai precisar

  1. Um par de credenciais client_id + client_secret (obtidas no painel — veja como obter sua API key).
  2. cURL instalado (já vem na maioria dos sistemas Unix, macOS e Windows 10+).
  3. Opcionalmente, jq para formatar e extrair campos do JSON de resposta.
  4. Um PDF de teste para enviar à assinatura.

Vamos usar o ambiente de homologação ao longo do guia. O host base de HML é api-hml.signdocs.com.br (com hífen — atenção, não é api.hml.signdocs.com.br). Em produção, troque apenas o host base e as credenciais.

Passo 1 — Obter o token OAuth2 (client-credentials)

Toda chamada à API exige um token Bearer. A SignDocs usa o fluxo OAuth2 client-credentials, apropriado para integrações server-to-server: você troca seu client_id e client_secret por um access_token de vida curta. Para o detalhamento do JWT (assinado com ECDSA ES256, chaves em KMS), expiração (15 minutos) e rotação, veja o guia profundo de autenticação OAuth2 na API.

A requisição é um POST com corpo no formato application/x-www-form-urlencoded:

# Passo 1: trocar client_id/secret por um access_token Bearer curl -s -X POST "https://api-hml.signdocs.com.br/oauth2/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=SEU_CLIENT_ID" \ -d "client_secret=SEU_CLIENT_SECRET" \ -d "scope=transactions:write transactions:read evidence:read webhooks:write"

Resposta típica (HTTP 200):

{ "access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjbGllbnQ...", "token_type": "Bearer", "expires_in": 900, "scope": "transactions:write transactions:read evidence:read webhooks:write" }

O campo expires_in indica a validade em segundos (900 = 15 minutos). Para automatizar, capture o token em uma variável de shell. Com jq:

# Capturar o token em uma variável reutilizável TOKEN=$(curl -s -X POST "https://api-hml.signdocs.com.br/oauth2/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=$SIGNDOCS_CLIENT_ID" \ -d "client_secret=$SIGNDOCS_CLIENT_SECRET" | jq -r '.access_token') # Conferir que o token foi obtido echo "${TOKEN:0:24}..."
Boas práticas com o token: nunca exponha o client_secret ou o access_token no front-end ou em repositórios. Trate-os como segredos de ambiente. Cacheie o token até perto de expires_in e renove proativamente; não solicite um token novo a cada requisição. Em produção, considere mTLS para clientes enterprise/regulados (BACEN/Open Finance), que adiciona autenticação mútua de certificado por cima do OAuth2.

Passo 2 — Criar a sessão de assinatura (POST /v1/signing-sessions)

A forma mais rápida de produzir uma assinatura é a Assinatura Expressa: uma única chamada a POST /v1/signing-sessions devolve uma sessão pronta com URL de checkout hospedado ou widget incorporável. (Para envelopes com múltiplos signatários, ordem de assinatura e ciclo de vida completo, veja o fluxo transacional da API.)

Repare em um detalhe importante do corpo JSON: você não envia as etapas — o policy.profile as determina. CLICK_PLUS_OTP, por exemplo, gera as etapas CLICK_ACCEPT, OTP_CHALLENGE e OTP_VERIFY; DIGITAL_CERTIFICATE exige certificado ICP-Brasil e gera a etapa DIGITAL_SIGN_A1. As etapas aparecem na resposta, já na ordem correta.

# Passo 2: criar a signing session (Assinatura Expressa) curl -s -X POST "https://api-hml.signdocs.com.br/v1/signing-sessions" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -H "X-Idempotency-Key: 7f3c1a2e-poc-0001" \ -d '{ "purpose": "DOCUMENT_SIGNATURE", "policy": { "profile": "DIGITAL_CERTIFICATE" }, "signer": { "name": "Maria Silva", "email": "maria@empresa.com.br", "userExternalId": "usr_12345", "cpf": "12345678901" }, "document": { "content": "'"$(base64 -w0 Contrato_Prestacao_Servicos.pdf)"'", "filename": "Contrato_Prestacao_Servicos.pdf" }, "owner": { "name": "Equipe Comercial", "email": "comercial@minhaempresa.com.br" }, "returnUrl": "https://minhaempresa.com.br/assinatura/concluida" }'

Resposta (HTTP 201 Created):

{ "sessionId": "01JC9K6M8P0R2T4V6X8Z0B2D4F", "transactionId": "01JC9K6M8P0R2T4V6X8Z0B2D4G", "status": "ACTIVE", "url": "https://sign-hml.signdocs.com.br/s/01JC9K6M8P0R2T4V6X8Z0B2D4F", "clientSecret": "ss_secret_4f2a1c9b...", "expiresAt": "2026-06-27T18:00:00Z" }
Atenção ao profile: em policy.profile use DIGITAL_CERTIFICATE. DIGITAL_SIGN_A1 é um tipo de passo (step.type), não um valor de profile — a API rejeita DIGITAL_SIGN_A1 como profile. Via API, a assinatura com certificado usa o A1 (em arquivo), em um protocolo de duas fases em que a chave privada nunca sai do lado do titular; para quem só possui token A3, o aplicativo SignDocs oferece o assinador desktop (Windows/macOS/Linux). A classe real do certificado fica registrada na evidência da assinatura.

Sobre a URL de compartilhamento e o clientSecret

O campo url sozinho não é o link de assinatura completo. Para o checkout hospedado, combine a url com o clientSecret como parâmetro de query ?cs= — esse é o link que o signatário abre. Para embutir a assinatura no seu front-end, o mesmo clientSecret alimenta o SDK oficial @signdocs-brasil/js, que abre o checkout como popup com callbacks de conclusão. Para o panorama do produto, veja a página da API de assinatura digital.

Passo 3 — Duas formas de enviar o PDF

No Passo 2 enviamos o documento inline em base64 (campo document.content), o caminho mais simples para arquivos de até 10 MB — o hash SHA-256 é calculado no servidor. Para arquivos maiores, ou quando você não quer trafegar o binário pelo seu backend, o fluxo transacional oferece o upload em duas etapas via URL pré-assinada, usando o transactionId retornado:

# Alternativa: upload em duas etapas (presigned URL) # 1) Solicitar a URL pré-assinada curl -s -X POST "https://api-hml.signdocs.com.br/v1/transactions/$TX_ID/document/presign" \ -H "Authorization: Bearer $TOKEN" # → { "uploadUrl": "https://...X-Amz-Signature=..." } # 2) PUT do binário do PDF diretamente na uploadUrl curl -s -X PUT "$UPLOAD_URL" \ -H "Content-Type: application/pdf" \ --data-binary "@/caminho/para/Contrato_Prestacao_Servicos.pdf" \ -w "\nHTTP status: %{http_code}\n" # 3) Confirmar o upload curl -s -X POST "https://api-hml.signdocs.com.br/v1/transactions/$TX_ID/document/confirm" \ -H "Authorization: Bearer $TOKEN"

Um HTTP 200 (ou 204) no PUT confirma o envio ao storage; a confirmação fecha o ciclo e o hash é calculado no servidor. A URL pré-assinada tem vida curta — se expirar antes do PUT, basta solicitar outra com o mesmo presign: nada se perde.

Entrega do link: a API só envia o e-mail de convite ao signatário quando o owner.email da requisição é diferente do signer.email. Se forem iguais (ou se você omitir o owner), entregue o link você mesmo combinando url + ?cs= + clientSecret. Para reenviar o convite por e-mail, use POST /v1/signing-sessions/{sessionId}/resend-invite.

Passo 4 — Consultar o status da assinatura

Para acompanhar o progresso de forma síncrona, faça um GET no recurso da sessão. (Em produção, prefira webhooks ao invés de polling — mais sobre isso no próximo passo.)

# Passo 4: consultar o status atual da sessão curl -s -X GET "https://api-hml.signdocs.com.br/v1/signing-sessions/$SESSION_ID" \ -H "Authorization: Bearer $TOKEN" | jq '.'

Resposta (HTTP 200) com a assinatura concluída:

{ "sessionId": "01JC9K6M8P0R2T4V6X8Z0B2D4F", "transactionId": "01JC9K6M8P0R2T4V6X8Z0B2D4G", "status": "COMPLETED", "expiresAt": "2026-06-27T18:00:00Z" } # Com a sessão COMPLETED, baixe o pacote de evidências (.p7m) # pela transação vinculada (escopo evidence:read): curl -s -X GET "https://api-hml.signdocs.com.br/v1/transactions/$TX_ID/evidence" \ -H "Authorization: Bearer $TOKEN" | jq '.downloadUrl' # → URL temporária do arquivo .p7m; baixe e arquive
Status Significado Próxima ação
ACTIVE Sessão criada e pronta; o signatário pode assinar Entregar o link (url?cs=...) ao signatário
COMPLETED Assinatura concluída; evidências disponíveis Baixar o .p7m via GET /v1/transactions/{id}/evidence
CANCELLED Sessão cancelada (via POST .../cancel) Tratar no fluxo de negócio (renegociar/recriar)
EXPIRED Janela de assinatura encerrou sem conclusão Recriar a sessão se ainda for necessário
FAILED Uma etapa obrigatória falhou (ex.: biometria reprovada) Oferecer outro perfil ou tratar a exceção no seu fluxo

O downloadUrl entrega o pacote de evidências em formato .p7m (PKCS#7/CMS), que serve como prova jurídica completa da assinatura — e a URL é temporária, então baixe e arquive. A finalização também devolve um evidenceId, que qualquer pessoa pode conferir de forma independente via GET /v1/verify/{evidenceId} (sem autenticação) ou no verificador público em verificador.signdocs.com.br. Para entender os padrões por trás dele, veja o glossário (PKCS#7/CMS) e o evidence pack .p7m como prova jurídica.

Passo 5 — Verificar a assinatura de um webhook (HMAC-SHA256)

Polling é útil para testes, mas em produção você quer receber webhooks: a API faz um POST HTTPS para o seu endpoint sempre que um evento ocorre (como SIGNING_SESSION.COMPLETED ou TRANSACTION.COMPLETED). Cada webhook traz uma assinatura HMAC-SHA256 no header, calculada sobre o corpo bruto da requisição usando um segredo compartilhado. Sua tarefa é recalcular o HMAC e comparar — isso garante que o evento veio mesmo da SignDocs e não de um atacante.

Primeiro, registre seu endpoint de webhook via REST:

# Registrar um endpoint de webhook (retorna o webhook secret) curl -s -X POST "https://api-hml.signdocs.com.br/v1/webhooks" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "url": "https://minhaempresa.com.br/webhooks/signdocs", "events": ["SIGNING_SESSION.COMPLETED", "TRANSACTION.COMPLETED", "SIGNING_SESSION.CANCELLED"] }'

A resposta inclui um secret — exibido uma única vez, no momento do registro. Guarde-o: é com ele que você valida cada webhook recebido. A verificação não depende de SDK — qualquer biblioteca de criptografia padrão calcula HMAC-SHA256. Mesmo a partir da linha de comando você pode reproduzir o cálculo com openssl para depurar:

# Depuração: recalcular o HMAC-SHA256 do payload bruto com openssl # A SignDocs assina "{timestamp}.{raw_body}" com o webhook secret TIMESTAMP="1750772000" RAW_BODY=$(cat webhook_payload.json) # corpo exatamente como recebido SECRET="SEU_WEBHOOK_SECRET" printf '%s.%s' "$TIMESTAMP" "$RAW_BODY" \ | openssl dgst -sha256 -hmac "$SECRET" -hex \ | sed 's/^.* //' # Compare o hex de saída com o header X-SignDocs-Signature recebido

No seu servidor, a lógica é a mesma em qualquer linguagem: pegue o corpo bruto (raw body, antes de qualquer parse de JSON), concatene com o timestamp do header, calcule o HMAC-SHA256 com o segredo e compare em modo timing-safe. Em Ruby, por exemplo, sem nenhum SDK:

# Receptor de webhook em Ruby puro (sem SDK) — exemplo Sinatra require 'sinatra' require 'openssl' WEBHOOK_SECRET = ENV['SIGNDOCS_WEBHOOK_SECRET'] TOLERANCE = 300 # 5 minutos contra replay post '/webhooks/signdocs' do raw_body = request.body.read signature = request.env['HTTP_X_SIGNDOCS_SIGNATURE'].to_s timestamp = request.env['HTTP_X_SIGNDOCS_TIMESTAMP'].to_s # 1. Validar timestamp (anti-replay) halt 401 if (Time.now.to_i - timestamp.to_i).abs > TOLERANCE # 2. Recalcular HMAC-SHA256 sobre "{timestamp}.{raw_body}" expected = OpenSSL::HMAC.hexdigest( 'SHA256', WEBHOOK_SECRET, "#{timestamp}.#{raw_body}" ) # 3. Comparação timing-safe halt 401 unless Rack::Utils.secure_compare(expected, signature) # 4. Autêntico: enfileirar e responder 200 rápido status 200 body '{"status":"received"}' end
Segurança crítica: sempre compare assinaturas em modo timing-safe (Rack::Utils.secure_compare em Ruby, hmac.compare_digest em Python, crypto.timingSafeEqual em Node.js). Comparação simples com == é vulnerável a timing attacks. Use também o corpo bruto da requisição no cálculo — reserializar o JSON quase sempre muda os bytes e invalida o HMAC.

O modelo de eventos, idempotência e a estratégia de retry com backoff exponencial estão detalhados no guia de webhooks e eventos da API. A regra de ouro: responda 200 rápido e processe o evento de forma assíncrona em uma fila.

Homologação vs. produção: o que muda no cURL

Todo o fluxo acima rodou em homologação. A boa notícia: o código cURL é praticamente idêntico em produção. O que muda:

Aspecto Homologação (HML) Produção
Host base api-hml.signdocs.com.br (com hífen) api.signdocs.com.br
Credenciais client_id/secret de teste client_id/secret de produção
Persistência TTL de 7 dias — entidades de teste são apagadas Persistência durável de transações e evidências
Cobrança Sem consumo do seu plano Conta no plano contratado
Assinaturas Válidas para teste de fluxo Validade jurídica plena (ICP-Brasil)
Lembrete do TTL de 7 dias: em HML, sessões, transações e evidências expiram automaticamente em 7 dias. Isso é ótimo para não acumular lixo de teste, mas não conte com dados de homologação de semanas atrás. Para validar persistência de longo prazo (como lookup no verificador), o comportamento durável só existe em produção.

Recapitulando o fluxo completo em cURL

Os cinco passos, do zero a uma assinatura com evidência jurídica, em REST puro:

  1. Token: POST /oauth2/token com grant_type=client_credentials → obtém o Bearer token (expira em 15 min).
  2. Sessão: POST /v1/signing-sessions com finalidade, política, signatário e documento em base64 → devolve sessionId, transactionId, url e clientSecret.
  3. Upload alternativo: para arquivos maiores, presignPUTconfirm na API transacional.
  4. Status: GET /v1/signing-sessions/{sessionId} (ou webhooks) → acompanha até COMPLETED; evidência via GET /v1/transactions/{id}/evidence.
  5. Webhook: recebe o POST de evento e valida o HMAC-SHA256 sobre {timestamp}.{raw_body}.

Esse mesmo contrato sustenta os SDKs oficiais. Quando você estiver pronto para uma camada mais ergonômica na sua linguagem — se ela tiver SDK — a transição é direta: o tutorial de Node.js e o de Python seguem exatamente esses cinco passos, só que com tipos e helpers. E para a visão completa do produto e dos endpoints, a página da API de assinatura digital é o ponto de partida.

SignDocs: REST puro, sem amarras de linguagem. A API SignDocs é nativa de ICP-Brasil, com base legal na MP 2.200-2/2001, LGPD-first e padrões PAdES/CAdES no nível baseline (assinatura + certificado + carimbo de hora do servidor + trilha de auditoria ancorada ao hash SHA-256). Funciona via cURL, SDK oficial ou no-code. O acesso à API é um plano sob medida, com sandbox de homologação gratuito — fale com nosso time para receber as credenciais, ou veja como obter sua API key em minutos pelo painel.

Perguntas Frequentes

Preciso de um SDK para integrar a API de assinatura digital?

Não. A API SignDocs é REST puro sobre HTTPS com JSON, então qualquer linguagem capaz de fazer uma requisição HTTP consegue integrar usando apenas cURL ou um cliente HTTP nativo. Existem SDKs oficiais para TypeScript/Node, Python, Go, Java, PHP e C#/.NET que economizam código repetitivo, mas eles são opcionais. Para linguagens sem SDK oficial, como Ruby, Elixir ou Rust, o caminho recomendado é consumir os endpoints REST diretamente, exatamente como mostramos com cURL neste guia.

Como obtenho o token OAuth2 com cURL?

Você faz um POST para o endpoint de token enviando grant_type=client_credentials junto com seu client_id e client_secret no formato application/x-www-form-urlencoded. A resposta traz um access_token do tipo Bearer com tempo de expiração em segundos no campo expires_in. Esse token deve ser enviado no header Authorization: Bearer em todas as chamadas seguintes. O fluxo client-credentials é server-to-server e o token nunca deve ser exposto no front-end.

Qual a diferença entre a Signing Session e a Transaction API?

A Assinatura Expressa (POST /v1/signing-sessions) cria, com uma única chamada, uma sessão de assinatura que devolve uma URL de checkout hospedado ou um widget incorporável — ideal para fluxos simples de um signatário. A Transaction API trabalha com envelopes completos, múltiplos signatários, ordem de assinatura e o ciclo de vida transacional inteiro. Para a primeira chamada e provas de conceito, a Signing Session é o caminho mais rápido; fluxos corporativos complexos costumam usar a Transaction API.

Como envio o PDF a ser assinado pela API REST?

Há dois caminhos. O mais simples é enviar o PDF inline, codificado em base64, no campo document.content (até 10 MB) — o hash SHA-256 é calculado no servidor. Para arquivos maiores, o fluxo transacional oferece o upload em duas etapas: POST /v1/transactions/{id}/document/presign devolve uma URL pré-assinada temporária, você faz um PUT do binário diretamente nela e confirma com POST .../document/confirm. Se a URL pré-assinada expirar, basta solicitar outra — nada se perde.

Como verifico se um webhook veio mesmo da SignDocs usando só REST?

Cada webhook chega como POST HTTPS com um header de assinatura HMAC-SHA256 calculado a partir do corpo bruto da requisição e de um segredo compartilhado. No seu endpoint, você recalcula o HMAC-SHA256 sobre o raw body usando o mesmo segredo e compara, com comparação timing-safe, contra o valor recebido. Se coincidir, o evento é autêntico. Valide também o timestamp para evitar replay. Isso independe de linguagem ou SDK: qualquer biblioteca de criptografia padrão calcula HMAC-SHA256.

Posso testar a API com cURL antes de ir para produção?

Sim. Use o ambiente de homologação com host api-hml.signdocs.com.br (com hífen, não api.hml). As credenciais e os endpoints são os mesmos do fluxo de produção, então o código cURL que funciona em HML funciona em produção apenas trocando o host base e as credenciais. Atenção: no ambiente HML as entidades têm TTL de 7 dias, ou seja, transações e evidências de teste são apagadas automaticamente após esse prazo.

A API de assinatura tem validade jurídica no Brasil mesmo via REST puro?

Sim. A forma de integração (cURL, SDK ou no-code) não altera a validade jurídica. O que confere validade é o padrão de assinatura aplicado e a coleta de evidências: a SignDocs é nativa de ICP-Brasil, ancorada na MP 2.200-2/2001, gera contêineres PAdES/CAdES no nível baseline (assinatura + certificado + carimbo de hora do servidor + trilha de auditoria) e evidence pack .p7m que vincula o hash SHA-256 do documento à identidade do signatário. A mesma assinatura qualificada ou avançada é produzida independentemente de você ter chamado o endpoint por cURL ou por um SDK oficial.

Faça sua primeira chamada à API em minutos

REST puro, SDKs oficiais para 6 linguagens, ambiente de homologação gratuito e validade jurídica nativa de ICP-Brasil. O acesso à API é um plano sob medida — gere suas credenciais de homologação e teste com cURL antes de qualquer contrato.

Fale com o time comercial Conheça a plataforma grátis