Como Integrar Assinatura Digital em Python

Se você precisa adicionar assinatura digital a uma aplicação Python — um backend Django, um microsserviço FastAPI ou um script de automação — este guia é um quickstart hands-on de ponta a ponta. Você vai instalar o SDK oficial Python da SignDocs via pip, autenticar com OAuth2 client-credentials, criar uma sessão de assinatura, adicionar um signatário com perfil de autenticação, enviar o documento, validar webhooks com HMAC-SHA256 em Flask/FastAPI e, por fim, baixar o PDF assinado.

Todos os trechos de código são Python idiomático e prontos para adaptar ao seu projeto. Ao final, você terá um fluxo completo e funcional, do envio à coleta do documento assinado com validade jurídica no Brasil. Se ainda está avaliando a plataforma, comece pelo panorama da API de assinatura digital da SignDocs e depois volte para colocar a mão na massa.

O Python é uma das linguagens com SDK oficial de primeira classe na SignDocs — ao lado de TypeScript/Node, Go, Java, PHP e C#/.NET. Isso significa autenticação, serialização e tratamento de erros já encapsulados, sem você precisar montar requisições HTTP cruas (embora isso também seja possível via REST, como mostramos na integração com cURL e REST).

Pré-requisitos

Antes de escrever a primeira linha de código, garanta que você tem:

  • Python 3.9+ instalado (recomendamos 3.11 ou superior).
  • Uma conta na SignDocs Brasil (PJ, para o self-service de credenciais). O sandbox de homologação é gratuito; o acesso de produção à API é contratado como plano sob medida com o time comercial.
  • Credenciais de API — um client_id e um client_secret gerados no painel. Se ainda não as tem, veja como obter sua API key de assinatura.
  • Um endpoint HTTPS público para receber webhooks (em desenvolvimento, um túnel como ngrok resolve).
  • Um ambiente virtual Python (venv ou poetry) — boa prática para isolar dependências.
Dica de ambiente: comece todo o desenvolvimento apontando para o ambiente de homologação, cujo host base é api-hml.signdocs.com.br. As entidades criadas lá têm TTL de 7 dias, o que mantém seus testes automatizados limpos. Saiba mais no guia de homologação e sandbox.

Passo 1 — Instalar o SDK oficial Python via pip

A instalação é feita com pip, dentro do seu ambiente virtual. O pacote traz o cliente, os modelos de dados (com type hints) e os utilitários de verificação de webhook.

# Criar e ativar um ambiente virtual python -m venv .venv source .venv/bin/activate # Linux/macOS # .venv\Scripts\activate # Windows # Instalar o SDK oficial Python da SignDocs pip install signdocs-brasil

Verifique a instalação e a versão importando o pacote no interpretador:

import signdocs_brasil print(signdocs_brasil.__version__)

Recomendamos fixar a versão no seu requirements.txt ou pyproject.toml para builds reprodutíveis. Atualizações do SDK seguem versionamento semântico, então mudanças incompatíveis só ocorrem em major releases.

Passo 2 — Autenticar com OAuth2 client-credentials

A API SignDocs usa o fluxo OAuth2 client-credentials: sua aplicação (um cliente de máquina, sem usuário interativo) troca client_id + client_secret por um bearer token de curta duração. Esse token é um JWT assinado com ECDSA (ES256), expira em 15 minutos e deve acompanhar cada requisição no header Authorization: Bearer. Para o detalhamento do protocolo, consulte o guia de autenticação OAuth2 na API.

Com o SDK (renovação automática)

O caminho mais simples é deixar o SDK cuidar da obtenção e renovação do token. Você instancia o cliente com as credenciais e ele gerencia o ciclo de vida do token internamente.

import os from signdocs_brasil import SignDocsBrasilClient, ClientConfig # Nunca hardcode segredos: use variáveis de ambiente client = SignDocsBrasilClient(ClientConfig( client_id=os.environ["SIGNDOCS_CLIENT_ID"], client_secret=os.environ["SIGNDOCS_CLIENT_SECRET"], # Aponte para homologação durante o desenvolvimento base_url="https://api-hml.signdocs.com.br", )) # O SDK obtém e renova o token automaticamente em cada chamada print("Cliente autenticado e pronto.")

Fluxo manual com requests (controle total)

Se você prefere controlar o cache do token — por exemplo, compartilhá-lo entre workers via Redis — pode implementar o fluxo manualmente. O exemplo abaixo usa apenas a biblioteca requests:

import os import time import requests TOKEN_URL = "https://api-hml.signdocs.com.br/oauth2/token" _token_cache = {"access_token": None, "expires_at": 0} def get_access_token(): """Retorna um bearer token válido, renovando quando necessário.""" if _token_cache["access_token"] and time.time() < _token_cache["expires_at"] - 60: return _token_cache["access_token"] response = requests.post( TOKEN_URL, data={ "grant_type": "client_credentials", "client_id": os.environ["SIGNDOCS_CLIENT_ID"], "client_secret": os.environ["SIGNDOCS_CLIENT_SECRET"], }, timeout=10, ) response.raise_for_status() payload = response.json() _token_cache["access_token"] = payload["access_token"] _token_cache["expires_at"] = time.time() + payload["expires_in"] return payload["access_token"]
Boa prática de segurança: mantenha client_secret fora do código-fonte. Use variáveis de ambiente, AWS Secrets Manager, HashiCorp Vault ou o gerenciador de segredos da sua plataforma. Para clientes enterprise/regulados (BACEN, Open Finance), a SignDocs também oferece mTLS (mutual TLS) além do OAuth2.

Passo 3 — Criar uma sessão de assinatura

A SignDocs expõe duas superfícies de API. A Transaction API trabalha com envelopes completos (múltiplos signatários, ordem de assinatura, ciclo de vida detalhado). A Assinatura Expressa (Signing Sessions) resolve o caso comum em uma única chamada POST /v1/signing-sessions, devolvendo um link de checkout hospedado ou um widget incorporável. Para o quickstart, vamos usar a Assinatura Expressa — é o caminho mais rápido para ver a tinta na página.

Carregamos o PDF, codificamos em base64 e criamos a sessão — signatário e política de autenticação vão na mesma chamada:

import base64 from pathlib import Path from signdocs_brasil.models import ( CreateSigningSessionRequest, SignerRequest, PolicyRequest, DocumentRequest, ) # 1. Ler o documento e codificar em base64 pdf_base64 = base64.b64encode( Path("contrato_prestacao_servicos.pdf").read_bytes() ).decode() # 2. Criar a sessão de assinatura (Assinatura Expressa) session = client.signing_sessions.create(CreateSigningSessionRequest( purpose="DOCUMENT_SIGNATURE", # CLICK_PLUS_OTP = aceite + código por e-mail/SMS; # use DIGITAL_CERTIFICATE para exigir certificado ICP-Brasil (A1) policy=PolicyRequest(profile="CLICK_PLUS_OTP"), signer=SignerRequest( name="Maria Silva", email="maria@empresa.com.br", user_external_id="usr_12345", # ID no SEU sistema cpf="12345678901", # 11 dígitos, sem pontuação ), document=DocumentRequest(content=pdf_base64, filename="contrato.pdf"), return_url="https://seuapp.com.br/assinatura/concluida", )) print("Sessão criada:", session.session_id) print("URL de assinatura:", f"{session.url}?cs={session.client_secret}")
Atenção ao perfil: o valor DIGITAL_CERTIFICATE aciona assinatura digital com certificado ICP-Brasil — via API, o certificado é A1 (em arquivo); titulares de token A3 usam o assinador desktop do aplicativo. Não confunda com DIGITAL_SIGN_A1, que é um tipo de etapa (step type) que aparece na resposta — nunca um valor de profile. Se quiser apenas assinatura eletrônica avançada (clique, OTP, biometria), use os perfis CLICK_ONLY, CLICK_PLUS_OTP, BIOMETRIC ou BIOMETRIC_PLUS_OTP — a lista completa está em o que é uma API de assinatura digital.

Passo 4 — Adicionar signatário e perfil de autenticação

Repare que o signatário já foi na criação da sessão — na Assinatura Expressa, cada sessão tem exatamente um signatário. O que você ajusta é o perfil de autenticação dele: cada perfil gera automaticamente as etapas correspondentes, como CLICK_ACCEPT (clickwrap), OTP_CHALLENGE/OTP_VERIFY (SMS/e-mail), BIOMETRIC_LIVENESS/BIOMETRIC_MATCH (facial) e DIGITAL_SIGN_A1 (certificado ICP-Brasil), que aparecem na resposta.

# O rigor é proporcional ao risco — basta trocar o profile: # Aceite com evidências (menor fricção) policy=PolicyRequest(profile="CLICK_ONLY") # Aceite + código de uso único por e-mail ou SMS policy=PolicyRequest(profile="CLICK_PLUS_OTP") # Biometria facial com prova de vida (+ OTP) policy=PolicyRequest(profile="BIOMETRIC_PLUS_OTP") # Certificado ICP-Brasil (assinatura qualificada) policy=PolicyRequest(profile="DIGITAL_CERTIFICATE")

Quando há mais de um signatário no mesmo documento, use a API de envelopes (client.envelopes.create + add_session, com o signer_index definindo a fila em modo sequencial). Para fluxos complexos com várias partes, consulte o guia de ordem de assinatura com múltiplos signatários.

Passo 5 — Enviar o documento para assinatura

Não há um passo de "envio" separado: a sessão nasce pronta (status ACTIVE) e a resposta da criação já traz a URL de assinatura. Você decide como entregá-la ao signatário:

# O link final é a url + "?cs=" + client_secret link_assinatura = f"{session.url}?cs={session.client_secret}" # Entregue pelo SEU canal (e-mail, WhatsApp, portal)... enviar_por_email("maria@empresa.com.br", link_assinatura) # ...ou informe owner={"name": ..., "email": ...} na criação da sessão # e a própria SignDocs envia o convite por e-mail ao signatário.
Hosted vs. embedded: com a Assinatura Expressa você pode redirecionar o signatário para um checkout hospedado pela SignDocs ou abrir o checkout como popup de dentro do seu front-end com o SDK JavaScript @signdocs-brasil/js, usando o client_secret. Para a experiência embutida, veja a página da Assinatura Expressa. Cuidado: se o remetente e o signatário forem a mesma pessoa, evite redirecionar automaticamente o remetente para a URL de assinatura.

Em fluxos transacionais mais ricos (envelopes com múltiplos signatários, reenvio de convite, cancelamento), a sequência é a mesma em espírito, mas usa a API de envelopes e transações. O fluxo transacional completo detalha cada transição de estado.

Passo 6 — Receber e verificar webhooks (HMAC-SHA256)

Em vez de ficar consultando o status repetidamente (polling), o ideal é registrar um webhook: a API faz um POST HTTPS para o seu endpoint sempre que um evento relevante ocorre — SIGNING_SESSION.COMPLETED, TRANSACTION.COMPLETED, STEP.FAILED, entre outros. Cada requisição vem com uma assinatura HMAC-SHA256 que você precisa validar antes de confiar no payload. O guia de webhooks e eventos da API cobre a arquitetura event-driven em profundidade.

Receptor com Flask

O ponto crítico é ler o corpo bruto (raw body) da requisição — não o JSON reparseado — porque o HMAC é calculado sobre os bytes exatos enviados pela API.

import os import hmac import hashlib import time from flask import Flask, request, jsonify app = Flask(__name__) WEBHOOK_SECRET = os.environ["SIGNDOCS_WEBHOOK_SECRET"] TIMESTAMP_TOLERANCE = 300 # 5 minutos def verify_signature(raw_body: bytes, signature: str, timestamp: str) -> bool: """Valida a assinatura HMAC-SHA256 do webhook.""" # 1. Bloquear replay attacks pela janela de timestamp if abs(int(time.time()) - int(timestamp)) > TIMESTAMP_TOLERANCE: return False # 2. Recalcular HMAC sobre timestamp + "." + corpo bruto signed_payload = timestamp.encode() + b"." + raw_body expected = hmac.new( WEBHOOK_SECRET.encode("utf-8"), signed_payload, hashlib.sha256, ).hexdigest() # 3. Comparação timing-safe contra timing attacks return hmac.compare_digest(expected, signature) @app.route("/webhooks/signdocs", methods=["POST"]) def handle_webhook(): raw_body = request.get_data() # bytes brutos, NÃO reparsear signature = request.headers.get("X-SignDocs-Signature", "") timestamp = request.headers.get("X-SignDocs-Timestamp", "") if not verify_signature(raw_body, signature, timestamp): return jsonify({"error": "Assinatura inválida"}), 401 event = request.get_json() # Idempotência: ignore se event["id"] já foi processado handle_event(event) # Responda 200 rápido; faça trabalho pesado de forma assíncrona return jsonify({"status": "received"}), 200

Receptor com FastAPI (async)

Em FastAPI, leia o corpo com await request.body(). O resto da lógica de verificação é idêntico:

import os, hmac, hashlib, time from fastapi import FastAPI, Request, HTTPException app = FastAPI() WEBHOOK_SECRET = os.environ["SIGNDOCS_WEBHOOK_SECRET"] TIMESTAMP_TOLERANCE = 300 def verify_signature(raw_body: bytes, signature: str, timestamp: str) -> bool: if abs(int(time.time()) - int(timestamp)) > TIMESTAMP_TOLERANCE: return False signed_payload = timestamp.encode() + b"." + raw_body expected = hmac.new(WEBHOOK_SECRET.encode(), signed_payload, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature) @app.post("/webhooks/signdocs") async def handle_webhook(request: Request): raw_body = await request.body() signature = request.headers.get("X-SignDocs-Signature", "") timestamp = request.headers.get("X-SignDocs-Timestamp", "") if not verify_signature(raw_body, signature, timestamp): raise HTTPException(status_code=401, detail="Assinatura inválida") event = await request.json() handle_event(event) return {"status": "received"}
Use o helper do SDK: o SDK oficial traz from signdocs_brasil import verify_webhook_signature — chame verify_webhook_signature(raw_body, signature, timestamp, secret) e ele encapsula o cálculo do HMAC (sobre timestamp + "." + corpo), a comparação timing-safe e a tolerância de timestamp de 5 minutos. Prefira-o para reduzir a chance de erros sutis. O exemplo manual acima serve quando você quer entender — ou auditar — exatamente o que acontece.

Passo 7 — Baixar o PDF assinado e o pacote de evidências

Quando o evento TRANSACTION.COMPLETED chega pelo webhook, o documento assinado está pronto. Você baixa o PDF — com assinatura PAdES embutida — e, opcionalmente, o pacote de evidências .p7m (PKCS#7/CMS) com a prova jurídica completa.

import requests from pathlib import Path def handle_event(event): # payload real: {id, eventType, tenantId, transactionId, timestamp, data} if event["eventType"] == "TRANSACTION.COMPLETED": tx_id = event["transactionId"] # 1. URLs temporárias do documento (o PDF carimbado vem em signed_url) dl = client.documents.download(tx_id) pdf = requests.get(dl.signed_url, timeout=30).content Path(f"assinados/{tx_id}.pdf").write_bytes(pdf) # 2. Pacote de evidências (.p7m): metadados + download via verificação evidencia = client.evidence.get(tx_id) downloads = client.verification.downloads(evidencia.evidence_id) if downloads.evidence_pack: p7m = requests.get(downloads.evidence_pack.url, timeout=30).content Path(f"assinados/{tx_id}.p7m").write_bytes(p7m) print("Documento assinado e evidências salvos.")

O pacote de evidências .p7m reúne o hash SHA-256 do documento, carimbo de hora do servidor, a trilha de auditoria com os dados de autenticação dos signatários e a cadeia de certificados ICP-Brasil — é a prova jurídica que sustenta a assinatura. Qualquer pessoa pode conferir a autenticidade do documento pelo verificador público da SignDocs.

SDK Python vs. REST puro: quando usar cada um

O SDK acelera 90% dos casos, mas há cenários em que chamar a REST diretamente faz sentido. A tabela resume o trade-off:

Aspecto SDK oficial Python REST puro (requests/httpx)
Autenticação OAuth2 Token obtido e renovado automaticamente Você implementa obtenção e cache do token
Type hints / autocompletar Modelos tipados com suporte de IDE Dicionários JSON sem tipagem
Verificação de webhook Helper pronto de HMAC-SHA256 Você escreve o cálculo do HMAC
Tratamento de erros Exceções específicas por código de erro Você inspeciona status e corpo manualmente
Velocidade de integração Mais rápida para o caso comum Mais verbosa, porém mais explícita
Controle fino / casos exóticos Limitado à superfície do SDK Acesso total a qualquer endpoint e header

Na prática, a maioria das equipes Python usa o SDK e cai para REST apenas em endpoints recém-lançados ou em edge cases. Se você prefere construir tudo sobre HTTP cru — útil para linguagens sem SDK, como Ruby — o guia de integração via cURL/REST mostra o caminho. Migrando de outra stack? Há um guia equivalente para integrar assinatura digital em Node.js.

Boas práticas para produção

Antes de promover sua integração de homologação para produção, revise estes pontos:

  • Troque o ambiente: aponte base_url e credenciais para produção. Lembre-se de que homologação tem TTL de 7 dias e não serve para dados reais.
  • Idempotência nos webhooks: webhooks têm semântica at-least-once. Deduplique pelo id do evento (em Redis ou banco) para não processar o mesmo evento duas vezes.
  • Responda rápido: retorne 200 em poucos segundos e empurre o processamento pesado para uma fila (SQS, Celery, RQ). Timeouts geram retentativas.
  • Retries com backoff: a API reenvia eventos com backoff exponencial. Seu endpoint deve tolerar entregas tardias e fora de ordem.
  • Segredos versionados: rotacione client_secret e o webhook secret periodicamente; nunca os comite no repositório.
  • Observabilidade: logue o id do evento, o transactionId e o status para reconciliar com o painel da SignDocs.
Validade jurídica no Brasil. A SignDocs é LGPD-first e ancorada na MP 2.200-2/2001 (ICP-Brasil). As assinaturas qualificadas usam certificados ICP-Brasil; as avançadas se apoiam no pacote de evidências. O tratamento de dados é documentado publicamente — prazos de guarda na Tabela de Retenção e instrumentos na Central de Confiança, com canal de DPO para direitos do titular.

Perguntas Frequentes

Existe um SDK oficial de assinatura digital para Python?

Sim. A SignDocs Brasil mantém SDKs oficiais para Python, TypeScript/Node, Go, Java, PHP e C#/.NET. O SDK oficial Python da SignDocs é instalado via pip e encapsula a autenticação OAuth2, a renovação de tokens, a serialização dos payloads e o tratamento de erros da API REST, permitindo que você crie sessões de assinatura, adicione signatários e baixe o PDF assinado com poucas linhas de código idiomático.

Como funciona a autenticação OAuth2 na API de assinatura em Python?

A API usa o fluxo OAuth2 client-credentials. Sua aplicação troca um client_id e client_secret pelo endpoint de token e recebe um bearer token de curta duração (15 minutos), um JWT assinado com ECDSA (ES256). Esse token é enviado no header Authorization: Bearer em cada requisição. O SDK oficial Python da SignDocs cuida da obtenção e renovação automática do token, mas você também pode implementar o fluxo manualmente com a biblioteca requests caso prefira controlar o cache do token.

Preciso de certificado ICP-Brasil para usar a API em Python?

Depende do nível de assinatura desejado. Para assinaturas eletrônicas avançadas com autenticação por clique, OTP ou biometria, não é necessário certificado: a prova jurídica vem do pacote de evidências. Para assinatura digital qualificada com certificado ICP-Brasil, o perfil DIGITAL_CERTIFICATE é aplicado e o signatário usa seu próprio certificado A1 no momento de assinar (titulares de token A3 usam o assinador desktop do aplicativo). Em ambos os casos, a integração Python é a mesma; muda apenas o profile configurado na sessão.

Como valido a assinatura HMAC-SHA256 de um webhook em Python?

Cada webhook chega com um header de assinatura calculado com HMAC-SHA256 sobre o corpo bruto da requisição usando um secret compartilhado. No seu receptor Flask ou FastAPI, leia o corpo bruto (sem reparse), recalcule o HMAC-SHA256 com hmac.new e a mesma chave, e compare com hmac.compare_digest para evitar timing attacks. Valide também o timestamp para impedir ataques de replay. Só processe o evento depois que a assinatura for confirmada.

Qual a diferença entre a Transaction API e a Assinatura Expressa em Python?

A API de envelopes atende fluxos com múltiplos signatários no mesmo documento (até 100), ordem de assinatura sequencial ou paralela e ciclo de vida detalhado. A Assinatura Expressa (Signing Sessions) é uma única chamada POST /v1/signing-sessions que devolve o link de checkout hospedado e o clientSecret para o checkout embutido via SDK JavaScript, perfeita para o quickstart e para integrações de signatário único. Em Python, ambas usam o mesmo SDK e a mesma autenticação OAuth2; você escolhe o recurso conforme a complexidade do fluxo.

Como faço o download do PDF assinado pela API em Python?

Após o evento TRANSACTION.COMPLETED chegar pelo webhook, chame client.documents.download(transaction_id): a resposta traz URLs temporárias, com o PDF carimbado (PAdES) em signed_url — baixe os bytes e grave em disco ou em um bucket. O pacote de evidências .p7m (PKCS#7/CMS), com a prova jurídica completa da transação, é obtido via client.evidence.get + client.verification.downloads.

Posso testar a integração Python em um ambiente de homologação?

Sim. A SignDocs oferece um ambiente de homologação (sandbox) cujo host base é api-hml.signdocs.com.br. Você usa credenciais de homologação separadas e aponta o SDK para esse host configurando a base URL. As entidades criadas em homologação têm TTL de 7 dias, então são ideais para testes automatizados sem poluir dados de produção. Ao validar o fluxo completo, basta trocar a base URL e as credenciais para produção.

Integre assinatura digital ao seu app Python hoje

Gere suas credenciais de homologação (o sandbox é gratuito) e comece a assinar documentos com validade jurídica em Python — do quickstart à produção, contratada como plano sob medida. SDK oficial via pip, OAuth2, webhooks HMAC-SHA256 e suporte em português.

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