Como Enviar um Documento para Assinatura via API

Enviar um documento para assinatura via API parece simples — e pode ser — mas há decisões importantes em cada etapa: como subir o PDF de forma segura e escalável, como adicionar signatários com o perfil de autenticação correto, como definir a ordem de assinatura e a expiração, e como disparar os convites. Este guia prático percorre todo o envio passo a passo, com requisições e respostas JSON reais e um exemplo de upload por presigned URL.

Vamos focar na etapa de envio do documento — o momento em que um arquivo sai do seu sistema e entra no fluxo de coleta de assinaturas. Se você ainda está montando o ambiente, vale conferir antes o panorama de como funciona uma API de assinatura digital e o guia primeiros passos com a API em 5 minutos.

Ao final, você saberá escolher entre os dois caminhos de envio do SignDocs — os envelopes (fluxos multi-signatário) e a Assinatura Expressa (uma chamada → link de assinatura hospedado) — e terá o código necessário para colocar o primeiro documento no ar.

Visão geral: o que significa "enviar um documento" via API

Enviar um documento para assinatura é, na prática, criar um recurso na plataforma que reúne três elementos: o arquivo a ser assinado, a lista de signatários com seus métodos de autenticação, e as regras de fluxo (perfil de autenticação, ordem, expiração). Quando esse recurso é confirmado, a plataforma dispara os convites e passa a emitir eventos de ciclo de vida.

O envio se decompõe em quatro grandes passos:

  1. Preparar o PDF — garantir um arquivo válido, leve e padronizado.
  2. Subir o arquivo — inline em base64 (até 10 MB) ou por presigned URL, direto para o armazenamento de objetos.
  3. Montar o fluxo — sessão única (Assinatura Expressa) ou envelope com uma sessão por signatário, cada um com seu perfil de autenticação.
  4. Disparar os convites — deixar a plataforma notificar os signatários ou distribuir os links você mesmo.
Pré-requisito: toda chamada de envio exige um token de acesso válido. Se ainda não configurou a autenticação, comece pelo fluxo OAuth2 client-credentials para obter o bearer token usado nos exemplos abaixo. Em ambiente de testes, use o host de homologação api-hml.signdocs.com.br — lembrando que entidades de HML têm TTL de 7 dias.

Passo 1: preparar o PDF para assinatura

O PDF é o formato canônico para assinatura digital porque suporta o padrão PAdES, em que a assinatura fica embutida no próprio arquivo, preservando aparência e validade jurídica. Antes de subir o documento, alguns cuidados evitam problemas adiante:

  • Achatar formulários (flatten): campos de formulário AcroForm não preenchidos podem conflitar com o posicionamento das assinaturas. Achate o que for conteúdo fixo.
  • Remover proteção por senha: PDFs criptografados ou com restrições de edição não podem receber assinatura. Desbloqueie antes do envio.
  • Conferir o tamanho: arquivos muito grandes (alta resolução de imagens escaneadas) tornam o upload e a verificação mais lentos. Comprima quando possível.
  • Validar antes de enviar: a plataforma roda uma checagem de qualidade no PDF durante o upload — arquivos corrompidos ou protegidos são rejeitados nesse momento, e diagnosticar isso cedo evita retrabalho.

Documentos em outros formatos também podem ser assinados, mas via container CAdES no padrão PKCS#7/CMS, que gera um arquivo de evidência separado. Para entender quando usar cada padrão, veja o detalhamento sobre PAdES, CAdES e contêineres PKCS#7/CMS. Na maioria dos casos, converter para PDF antes do envio entrega o melhor resultado: um documento assinado autocontido e fácil de verificar publicamente.

Passo 2: subir o arquivo com presigned URL

Existem duas formas de levar os bytes do documento até a plataforma: inline em base64 dentro do JSON da chamada de criação (limite de 10 MB), ou upload por presigned URL. Para arquivos maiores ou volume de produção, a segunda é a abordagem recomendada. No SignDocs, o fluxo de presigned URL é ancorado na transação: você cria a sessão sem o documento e sobe o arquivo em seguida.

Por que presigned URL?

Uma presigned URL é um endereço temporário e assinado que autoriza o upload direto para o armazenamento de objetos, sem que o arquivo trafegue pelo corpo da requisição da API. Isso traz vantagens concretas:

  • Escala: a API não precisa receber nem reencaminhar megabytes; os bytes vão direto ao storage.
  • Segurança: a URL expira em poucos minutos e é válida apenas para aquele objeto e método (PUT).
  • Performance: sem overhead de base64 (que infla o payload em ~33%) e sem pressão de memória no seu backend.
  • Confiabilidade: uploads grandes não esbarram em limites de tamanho de requisição da API.

2.1 — Criar a sessão sem o documento

Primeiro você cria a sessão de assinatura normalmente — apenas sem o campo document. A transação subjacente nasce no estado CREATED, aguardando o arquivo:

# Cria a sessão sem documento (a transação fica em CREATED) curl -X POST https://api-hml.signdocs.com.br/v1/signing-sessions \ -H "Authorization: Bearer SEU_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "purpose": "DOCUMENT_SIGNATURE", "policy": { "profile": "CLICK_PLUS_OTP" }, "signer": { "name": "Maria Silva", "email": "maria@empresa.com.br", "cpf": "12345678901" } }' # Resposta (resumida): guarde o transactionId para o upload { "sessionId": "01JC8Z3M9QK4T2V7X1B5N6P8R0", "transactionId": "01JC8Z3M9QK4T2V7X1B5N6P8R1", "url": "https://sign-hml.signdocs.com.br/s/01JC8Z3M9QK4T2V7X1B5N6P8R0", "clientSecret": "ss_secret_..." }

2.2 — Solicitar a presigned URL

Com o transactionId em mãos, você pede o destino de upload, informando os metadados do arquivo:

# Solicita a URL de upload assinada (validade de 10 minutos) curl -X POST https://api-hml.signdocs.com.br/v1/transactions/01JC8Z3M9QK4T2V7X1B5N6P8R1/document/presign \ -H "Authorization: Bearer SEU_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "filename": "Contrato_Prestacao_Servicos.pdf", "contentType": "application/pdf" }' # Resposta { "uploadUrl": "https://s3.amazonaws.com/...?X-Amz-Signature=...", "uploadToken": "3f9c1a...", "expiresIn": 600, "contentType": "application/pdf", "instructions": "PUT the document to uploadUrl, then call POST /v1/transactions/{id}/document/confirm" }

2.3 — Enviar os bytes e confirmar o upload

Agora você faz um PUT direto na uploadUrl, enviando o arquivo binário. Esta chamada vai para o storage, não para a API — e por isso não leva o header Authorization; a autorização já está embutida na própria URL assinada. Em seguida, a chamada de confirmação (com o uploadToken) avisa a API que o arquivo chegou: é nela que o servidor calcula o hash SHA-256 do documento e move a transação para DOCUMENT_UPLOADED.

# 1) Envia o arquivo binário diretamente ao storage curl -X PUT "https://s3.amazonaws.com/...?X-Amz-Signature=..." \ -H "Content-Type: application/pdf" \ --data-binary @Contrato_Prestacao_Servicos.pdf # 2) Confirma o upload — o servidor calcula o hash SHA-256 curl -X POST https://api-hml.signdocs.com.br/v1/transactions/01JC8Z3M9QK4T2V7X1B5N6P8R1/document/confirm \ -H "Authorization: Bearer SEU_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "uploadToken": "3f9c1a..." }' # Resposta { "status": "DOCUMENT_UPLOADED" }

Em Node.js, o fluxo completo fica assim:

import { readFile } from 'node:fs/promises'; const API = 'https://api-hml.signdocs.com.br/v1'; const TOKEN = process.env.SIGNDOCS_TOKEN; const auth = { 'Authorization': `Bearer ${TOKEN}`, 'Content-Type': 'application/json' }; async function createSessionWithUpload(path, signer) { const bytes = await readFile(path); // 1. Cria a sessão sem o documento const r1 = await fetch(`${API}/signing-sessions`, { method: 'POST', headers: auth, body: JSON.stringify({ purpose: 'DOCUMENT_SIGNATURE', policy: { profile: 'CLICK_PLUS_OTP' }, signer }) }); const { sessionId, transactionId, url, clientSecret } = await r1.json(); // 2. Solicita a presigned URL const r2 = await fetch(`${API}/transactions/${transactionId}/document/presign`, { method: 'POST', headers: auth, body: JSON.stringify({ filename: 'Contrato.pdf', contentType: 'application/pdf' }) }); const { uploadUrl, uploadToken } = await r2.json(); // 3. PUT direto no storage (sem Authorization) await fetch(uploadUrl, { method: 'PUT', headers: { 'Content-Type': 'application/pdf' }, body: bytes }); // 4. Confirma o upload — o servidor calcula o hash SHA-256 await fetch(`${API}/transactions/${transactionId}/document/confirm`, { method: 'POST', headers: auth, body: JSON.stringify({ uploadToken }) }); return { sessionId, signingLink: `${url}?cs=${clientSecret}` }; }
Alternativa inline: para documentos de até 10 MB, embuta o arquivo como document.content (base64) na própria chamada de criação da sessão ou do envelope, eliminando presign e confirmação. É o caminho mais comum para começar; migre para o presigned URL quando os arquivos crescerem. Há também o upload direto em base64 pós-criação, via POST /v1/transactions/{id}/document.

Passo 3: montar o fluxo — sessão única ou envelope multi-signatário

Com o documento no lugar, o envio ganha forma: quem assina, como se autentica, em que ordem e até quando. Para um único signatário, a própria sessão de assinatura já resolve tudo. Para múltiplos signatários, o recurso certo é o envelope: o documento sobe uma vez e cada signatário recebe a sua própria sessão.

3.1 — O perfil de autenticação de cada signatário

Cada signatário recebe um perfil de autenticação (campo policy.profile), que define o nível de garantia exigido: CLICK_ONLY (aceite por clique), CLICK_PLUS_OTP (clique + código por e-mail/SMS), BIOMETRIC e BIOMETRIC_PLUS_OTP (biometria facial, sem ou com OTP) e DIGITAL_CERTIFICATE (certificado ICP-Brasil). Internamente, cada perfil vira uma sequência fixa de etapas (CLICK_ACCEPT, OTP_CHALLENGE, BIOMETRIC_LIVENESS, BIOMETRIC_MATCH...) que você vê na resposta da transação.

Atenção a um erro comum: DIGITAL_SIGN_A1 é um step.type (tipo de etapa que aparece na resposta), nunca um valor de profile. Enviar DIGITAL_SIGN_A1 como profile resulta em erro 400. Para exigir certificado ICP-Brasil, use o profile DIGITAL_CERTIFICATE — via API, a assinatura usa o certificado A1 do próprio titular; tokens A3 são atendidos pelo assinador desktop do SignDocs, fora do fluxo da API. Para combinar métodos, veja a autenticação multimétodo.

3.2 — Definir a ordem: sequencial ou paralela

O campo signingMode, definido na criação do envelope, controla a dinâmica entre os signatários:

  • PARALLEL: todos os signatários podem assinar a qualquer momento, em qualquer ordem. Ideal para concluir o documento o mais rápido possível.
  • SEQUENTIAL: a API impõe a fila definida pelo signerIndex de cada sessão (um inteiro de 1 a N) — o signatário 2 só consegue concluir depois que o 1 termina. Use quando houver dependência hierárquica de assinaturas.

Para um aprofundamento em cenários com muitas partes, papéis e dependências, consulte o guia de ordem de assinatura com múltiplos signatários.

3.3 — Personalizar a aparência (opcional)

A criação aceita um objeto appearance com a identidade visual do envio — companyName, logoUrl, brandColor, entre outros — aplicado à página hospedada que o signatário vê. Como a personalização é por envio, a mesma conta atende marcas ou clientes finais diferentes, o que é a base dos cenários white-label.

3.4 — Expiração e lembretes

No envio você define a janela de expiração — até quando os links são válidos — via expiresInMinutes no envelope. A API não tem lembretes recorrentes automáticos: o ritmo de cobrança fica no seu controle, com o evento TRANSACTION.DEADLINE_APPROACHING avisando via webhook quando o prazo de um signatário aperta e POST /v1/signing-sessions/{sessionId}/resend-invite reenviando o convite por e-mail.

3.5 — O envelope completo, na prática

# 1) Criar o envelope — o documento sobe uma única vez curl -X POST https://api-hml.signdocs.com.br/v1/envelopes \ -H "Authorization: Bearer SEU_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Idempotency-Key: envio-contrato-2026-08-0001" \ -d '{ "signingMode": "SEQUENTIAL", "totalSigners": 2, "document": { "content": "JVBERi0xLjQKJ...", "filename": "Contrato_Prestacao_Servicos.pdf" }, "expiresInMinutes": 4320, "owner": { "name": "Comercial", "email": "comercial@suaempresa.com.br" } }' # Resposta { "envelopeId": "01JC9A2B3C4D5E6F7G8H9J0K1L", "status": "CREATED", "documentHash": "a1b2c3...", "totalSigners": 2 } # 2) Uma sessão por signatário — signerIndex define a fila no modo sequencial curl -X POST https://api-hml.signdocs.com.br/v1/envelopes/01JC9A2B3C4D5E6F7G8H9J0K1L/sessions \ -H "Authorization: Bearer SEU_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "signerIndex": 1, "signer": { "name": "Maria Silva", "email": "maria@empresa.com.br", "cpf": "12345678901" }, "policy": { "profile": "DIGITAL_CERTIFICATE" } }' # Repita para o signatário 2 (signerIndex: 2 — outro perfil, se fizer sentido) # Resposta de cada sessão: o link de assinatura daquele signatário { "sessionId": "01JC9A3F8K2M4P6R8T0V2X4Z6B", "transactionId": "01JC9A3F8K2M4P6R8T0V2X4Z6C", "status": "ACTIVE", "url": "https://sign-hml.signdocs.com.br/s/01JC9A3F8K2M4P6R8T0V2X4Z6B", "clientSecret": "ss_secret_...", "expiresAt": "2026-08-12T18:00:00Z" }

Note o header X-Idempotency-Key: reenviar a mesma requisição com o mesmo corpo (após um timeout de rede, por exemplo) devolve a resposta original em vez de criar um envelope duplicado — a chave vale por 24 horas, e reutilizá-la com um corpo diferente resulta em 409. Repare também que cada signatário pode ter um perfil de autenticação diferente: o diretor com certificado ICP-Brasil, a testemunha com OTP. No modo sequencial, o signatário 2 até abre o link, mas só consegue concluir depois que o 1 termina. O desenho completo dos dois modos está no guia de ordem de assinatura com múltiplos signatários.

Passo 4: disparar os convites (ou entregar o link você mesmo)

Quando o envelope tem um owner (remetente) definido, a plataforma envia o convite por e-mail automaticamente a cada signatário cujo e-mail seja diferente do e-mail do owner. Mas há cenários em que você prefere controlar a distribuição:

  • Convite automático por e-mail: defina o owner do envelope e informe o e-mail de cada signatário. Simples e sem código adicional.
  • Entrega por canal próprio: monte o link final combinando a url da sessão com o clientSecret como parâmetro ?cs= e distribua por WhatsApp, app, portal ou e-mail transacional seu.
  • Assinatura embarcada: em fluxos com assinatura incorporada, abra o checkout em um pop-up dentro da sua interface com a biblioteca @signdocs-brasil/js, sem depender do e-mail.

A partir do envio, sua aplicação deve reagir aos eventos do ciclo de vida em vez de ficar consultando o status em loop. Configure um receptor para acompanhar SIGNING_SESSION.COMPLETED, ENVELOPE.ALL_SIGNED, TRANSACTION.EXPIRED e afins — o mecanismo completo está no guia de webhooks e eventos da API.

Envelopes vs. Assinatura Expressa: qual usar para enviar?

O SignDocs expõe duas superfícies de envio. Escolher a certa economiza código e evita complexidade desnecessária.

Envelopes (multi-signatário)

É o caminho para fluxos completos: múltiplos signatários, ordem sequencial ou paralela, perfis de autenticação distintos por signatário, eventos de ciclo de vida e evidências formais. É o que usamos nos exemplos acima. Para o desenho de ponta a ponta, veja o fluxo transacional completo.

Assinatura Expressa (Signing Sessions)

Resolve o caso "um documento, um signatário" em uma única chamada a POST /v1/signing-sessions, devolvendo o link de checkout hospedado (e o clientSecret para fluxos embarcados via pop-up). Ideal para a menor fricção possível — pense em onboarding, termos de aceite ou um contrato pontual:

# Envio em uma única chamada (Assinatura Expressa) curl -X POST https://api-hml.signdocs.com.br/v1/signing-sessions \ -H "Authorization: Bearer SEU_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "purpose": "DOCUMENT_SIGNATURE", "policy": { "profile": "DIGITAL_CERTIFICATE" }, "signer": { "name": "Maria Silva", "email": "maria@empresa.com.br", "cpf": "12345678901" }, "document": { "content": "JVBERi0xLjQKJ...", "filename": "Contrato.pdf" } }'

A resposta já entrega a url de assinatura e o clientSecret — o link final é url?cs=... —, sem o passo de montar um envelope.

Critério Envelopes Assinatura Expressa
Caso de uso Fluxos completos, multi-signatário, contratos formais Um documento, um fluxo, máxima simplicidade
Chamadas para enviar Criação do envelope (com o documento) + uma sessão por signatário Uma chamada a /v1/signing-sessions
Ordem de assinatura SEQUENTIAL ou PARALLEL, com signerIndex por sessão Focada em um signatário por sessão
Entrega ao signatário E-mail automático (via owner) ou URL própria Checkout hospedado ou pop-up incorporado
Eventos / webhooks Ciclo de vida completo do envelope Eventos da sessão de assinatura
Quando escolher Controle granular e várias partes Menor fricção e integração rápida
SignDocs: envie o primeiro documento em minutos. SDKs oficiais para TypeScript/Node, Python, Go, Java, PHP e C#/.NET cobrem autenticação OAuth2, sessões, envelopes e verificação de webhooks com tipagem completa. Para Ruby ou outras stacks, a API REST é totalmente agnóstica de linguagem. Crie sua conta grátis ou fale com o time comercial sobre um plano sob medida.

Boas práticas e erros comuns ao enviar documentos

  • Use X-Idempotency-Key na criação: reenviar o mesmo corpo devolve a resposta original em vez de duplicar o envio; a chave vale por 24 horas.
  • Não envie base64 para arquivos grandes: o limite do inline é 10 MB, e o presigned URL evita o overhead de ~33% do base64.
  • Não use DIGITAL_SIGN_A1 como profile: ele é um step.type; para certificado ICP-Brasil use DIGITAL_CERTIFICATE.
  • Teste em homologação primeiro: use api-hml.signdocs.com.br (atenção ao TTL de 7 dias das entidades) antes de ir para produção.
  • Registre seu webhook antes do primeiro envio: com o endpoint cadastrado desde o início, você não perde eventos do ciclo de vida.
  • Valide o PDF antes do upload: arquivos protegidos por senha ou corrompidos são rejeitados na checagem de qualidade do upload — validar antes evita o vaivém.
  • Trate a expiração da presigned URL: a URL vive 10 minutos; gere-a logo antes do PUT, não com horas de antecedência.

Cada documento enviado e concluído resulta em um arquivo assinado com validade jurídica respaldada pela MP 2.200-2/2001 (ICP-Brasil) e processamento em conformidade com a LGPD. O resultado pode ser verificado de forma independente no verificador público.

Perguntas Frequentes

Posso enviar o PDF em base64 dentro do JSON em vez de usar presigned URL?

Sim — e para a maioria das integrações é o caminho mais simples: o campo document.content da chamada de criação aceita o arquivo em base64, com limite de 10 MB. Acima disso, ou para evitar o overhead de ~33% do base64 em volume de produção, use o fluxo de presigned URL: crie a sessão sem o documento, solicite a URL assinada em POST /v1/transactions/{id}/document/presign, faça o PUT dos bytes direto no armazenamento de objetos e confirme em POST /v1/transactions/{id}/document/confirm — momento em que o servidor calcula o hash SHA-256 do arquivo.

Qual a diferença entre enviar por envelope e pela Assinatura Expressa?

O envelope é o caminho para fluxos com múltiplos signatários: você cria o envelope com o documento e o signingMode (SEQUENTIAL ou PARALLEL) e adiciona uma sessão por signatário via POST /v1/envelopes/{id}/sessions. A Assinatura Expressa, exposta em POST /v1/signing-sessions, resolve o caso de um documento e um signatário em uma única chamada, devolvendo a URL de assinatura hospedada e o clientSecret. Use envelopes quando houver várias partes e ordem de assinatura; use a Assinatura Expressa quando quiser a menor fricção possível para coletar uma assinatura.

Que formato de documento posso enviar para assinatura?

O PDF é o formato canônico para assinatura, pois recebe assinatura no padrão PAdES embutida no próprio arquivo, mantendo aparência e validade jurídica. Outros formatos — documentos Office (DOCX, XLSX, PPTX), OpenDocument, imagens (JPG, PNG, TIFF), TXT, CSV, XML, JSON e HTML — são aceitos e assinados via container CAdES no padrão PKCS#7/CMS, gerando um arquivo de assinatura separado do original. Na prática, recomenda-se converter para PDF antes do envio sempre que o objetivo for um documento assinado autocontido e fácil de verificar publicamente.

Como defino se os signatários assinam em sequência ou em paralelo?

A ordem é controlada pelo signingMode do envelope, definido na criação. No modo PARALLEL, todos os signatários podem assinar a qualquer momento, em qualquer ordem. No modo SEQUENTIAL, a fila é definida pelo signerIndex de cada sessão — um inteiro de 1 a N — e o signatário 2 só consegue concluir depois que o 1 termina. Escolha sequencial quando houver dependência hierárquica de assinaturas e paralelo quando quiser concluir o documento o mais rápido possível.

O que acontece se um documento não for assinado dentro do prazo de expiração?

Ao atingir a expiração, os links de assinatura pendentes deixam de ser válidos e sua aplicação é notificada via webhook pelos eventos TRANSACTION.EXPIRED, SIGNING_SESSION.EXPIRED e, em envelopes, ENVELOPE.EXPIRED — permitindo criar um novo envio ou disparar um fluxo de reengajamento. Antes disso, o evento TRANSACTION.DEADLINE_APPROACHING avisa quando o prazo de um signatário está apertando, e POST /v1/signing-sessions/{sessionId}/resend-invite reenvia o convite por e-mail. A janela de expiração é definida no envio (expiresInMinutes, no caso dos envelopes).

Posso personalizar a aparência da página de assinatura no envio?

Sim. A criação da sessão ou do envelope aceita um objeto appearance com a identidade visual do envio — nome da empresa, logotipo e cores (companyName, logoUrl, brandColor, entre outros) — aplicado à página hospedada que o signatário vê. Como a personalização é por envio, a mesma conta atende marcas ou clientes finais diferentes, o que é útil em cenários white-label. Já o posicionamento do carimbo visual dentro do PDF não é configurável por coordenadas: a plataforma aplica a assinatura digital ao documento e registra os dados do signatário na trilha de evidências.

Como envio o convite ao signatário: a API manda o e-mail ou eu entrego o link?

Os dois modelos são suportados. Se o envio tiver um owner (remetente) definido, a plataforma envia o convite por e-mail automaticamente a cada signatário cujo e-mail seja diferente do e-mail do owner. Se preferir distribuir por canal próprio (WhatsApp, portal, e-mail transacional seu), monte o link final combinando a url da sessão com o clientSecret como parâmetro ?cs= e entregue como quiser. Em integrações embarcadas, é comum abrir o checkout em um pop-up na sua interface com a biblioteca @signdocs-brasil/js, sem depender do e-mail.

Envie seu primeiro documento para assinatura via API hoje

Upload por presigned URL, envelopes para fluxos multi-signatário e Assinatura Expressa em uma chamada — com SDKs oficiais e suporte em pt-BR. Tudo com validade jurídica ICP-Brasil, conformidade LGPD e sandbox gratuito para desenvolver.

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