Códigos de Erro e Tratamento de Falhas na API de Assinatura

Uma integração robusta com uma API de assinatura digital não se mede pelos caminhos felizes, mas por como ela reage quando algo dá errado. Tokens expiram, payloads chegam malformados, picos de tráfego batem em limites de taxa e, ocasionalmente, o servidor responde com 5xx. Este guia é uma referência técnica completa sobre os códigos de erro HTTP da API SignDocs, o formato do corpo de erro, as causas mais comuns, a estratégia de retry correta e como debugar falhas em produção.

Se você ainda está formando uma visão geral, comece pela documentação central da API de assinatura digital e pelo quickstart de 5 minutos. Aqui partimos do princípio de que você já faz chamadas e quer endurecer o tratamento de erros.

Tratar erros bem não é um detalhe de polimento: é o que mantém transações de assinatura consistentes, evita cobranças duplicadas, mantém o usuário final informado e poupa horas de suporte. Vamos do contrato de erro genérico até os códigos específicos, com tabela de referência e um handler completo ao final.

A anatomia de uma resposta de erro

Toda chamada à API SignDocs retorna um código de status HTTP e, quando há falha, um corpo JSON estruturado. Antes de discutir cada código, é fundamental entender a forma desse corpo, porque é nele que está a informação acionável: o que deu errado, em qual campo e como rastrear o problema.

// Resposta 400 — erro de validação (RFC 7807) // HTTP/1.1 400 Bad Request // Content-Type: application/problem+json { "type": "https://api-hml.signdocs.com.br/errors/bad-request", "title": "Bad Request", "status": 400, "detail": "policy.profile: DIGITAL_SIGN_A1 não é um valor aceito. Use DIGITAL_CERTIFICATE.", "instance": "/v1/signing-sessions" }

Os campos que você deve sempre ler e registrar são:

O formato segue a RFC 7807 (Problem Details for HTTP APIs), entregue com Content-Type: application/problem+json. Os campos que você deve sempre ler e registrar são:

  • type: a URI estável que identifica a categoria do erro (ex.: https://api.signdocs.com.br/errors/bad-request). É o identificador legível por máquina — use-o, junto com o status, na sua lógica de tratamento.
  • title: o nome curto da categoria ("Bad Request", "Conflict"), estável por tipo de erro.
  • status: o código HTTP repetido no corpo, conveniente quando o status já foi consumido pelo cliente HTTP.
  • detail: a explicação específica daquele erro, em linguagem natural — útil para logs e para exibir ao operador, mas nunca para lógica de controle (o texto pode mudar).
  • instance: o caminho da requisição que gerou o erro, para correlação.
  • Correlação com o suporte: envie um cabeçalho x-correlation-id próprio (um UUID por requisição) — a API o aceita e ele correlaciona os seus logs com os do SignDocs. Registre-o junto com endpoint, horário em UTC e status.
Regra de ouro: trate erros pelo status HTTP e pela URI do type, nunca por substring do detail. Textos podem ser reescritos a qualquer momento; o status e o type fazem parte do contrato da API.

As classes de código de status HTTP

A API segue a semântica HTTP padrão, o que torna o comportamento previsível independentemente da linguagem do seu SDK. Em alto nível:

  • 2xx — Sucesso: a requisição foi aceita e processada. 200 OK para leituras e operações, 201 Created ao criar um recurso (envelope, signing session, transação).
  • 4xx — Erro do cliente: algo na sua requisição está errado — payload, token, permissão ou estado do recurso. Repetir sem corrigir não resolve.
  • 5xx — Erro do servidor: a falha está do lado da API ou da infraestrutura. São, em geral, transitórios e elegíveis para retry com backoff.

A distinção 4xx vs. 5xx é a base de toda a sua lógica de retry: 4xx (com exceção de 429 e 408) é determinístico e não deve ser repetido cegamente; 5xx costuma se resolver sozinho na segunda tentativa.

Tabela de referência de códigos de erro

A tabela abaixo é a referência rápida que você vai querer manter aberta durante a integração. Ela resume o significado de cada código no contexto da API de assinatura, a causa mais frequente e a ação recomendada.

Código Significado Causa comum na API de assinatura Ação
400 Bad Request Requisição malformada ou com valor inválido Campo obrigatório ausente; profile com valor inválido (ex.: DIGITAL_SIGN_A1); JSON malformado Corrigir o payload. Não repetir sem alterar
401 Unauthorized Falha de autenticação Token ausente, malformado ou expirado Obter novo access token e repetir
403 Forbidden Sem permissão / bloqueado Escopo insuficiente; recurso de outro tenant; WAF bloqueando webhook com IP privado Revisar permissões/escopo; usar URL pública. Não repetir
404 Not Found Recurso inexistente ID errado; recurso expirado (HML tem TTL de 7 dias); ambiente trocado (HML vs. prod) Verificar ID e ambiente. Não repetir
409 Conflict Conflito de estado TransactionConflict em criação concorrente de sessões; tentar agir sobre recurso já finalizado/cancelado Retry curto com backoff (concorrência); ou aceitar o estado
422 Unprocessable Entity Sintaxe válida, semântica inválida Combinação de etapas incompatível; signatário sem método de autenticação válido; ordem inconsistente Corrigir as regras de negócio do payload. Não repetir
429 Too Many Requests Limite de taxa atingido Excesso de requisições por janela; rajada acima da cota do plano Respeitar Retry-After e repetir com backoff
500 Internal Server Error Erro interno inesperado Exceção não tratada no servidor; condição transitória Retry com backoff exponencial
502 / 503 / 504 Gateway/indisponibilidade/timeout Deploy em andamento; cold start; sobrecarga momentânea Retry com backoff. Verificar página de status

400 Bad Request: erros de validação

O 400 é o erro mais comum durante a fase de integração. Ele significa que a API recebeu sua requisição, mas não conseguiu aceitá-la por um problema de formato ou de valor. As causas mais frequentes:

  • Campo obrigatório ausente: faltou signer, purpose, document.content ou outro campo exigido pelo endpoint.
  • JSON malformado: vírgula sobrando, aspas erradas, Content-Type que não é application/json.
  • Valor de enum inválido: o caso clássico — enviar DIGITAL_SIGN_A1 como policy.profile.

O erro clássico: DIGITAL_SIGN_A1 como profile

Esse é, de longe, o equívoco mais recorrente de quem começa a integrar. O campo policy.profile define o perfil de assinatura e aceita valores como DIGITAL_CERTIFICATE (assinatura com certificado ICP-Brasil). Já DIGITAL_SIGN_A1 é um step.type, que descreve uma etapa do fluxo na resposta da API — nunca um valor de profile na requisição.

// ERRADO — retorna 400 { "policy": { "profile": "DIGITAL_SIGN_A1" } // ✗ profile inválido } // CORRETO { "policy": { "profile": "DIGITAL_CERTIFICATE" } // ✓ } // DIGITAL_SIGN_A1 aparecerá como step.type na resposta, // descrevendo a etapa de assinatura com certificado ICP-Brasil.

Para entender quais perfis combinam quais métodos (clickwrap, OTP, biometria, certificado), veja a seção de métodos de autenticação em o que é uma API de assinatura digital.

401 Unauthorized: autenticação

O 401 indica que a API não conseguiu autenticar a requisição. Não é um problema de permissão (isso é 403) — é "não sei quem você é". As três causas:

  • Token expirado: a causa número um. Os access tokens emitidos pelo fluxo OAuth2 client-credentials expiram em 15 minutos por design. Se você cacheia o token, ele eventualmente expira.
  • Token ausente: faltou o header Authorization: Bearer <token>.
  • Token malformado: o JWT foi truncado, corrompido ou pertence a outro ambiente.

A solução para token expirado é simples e deve ser automática: detectar o 401, solicitar um novo token ao endpoint de autenticação e repetir a requisição uma vez. O fluxo completo de obtenção e renovação de token está detalhado no guia de autenticação OAuth2 da API de assinatura.

Padrão recomendado: implemente um interceptor no cliente HTTP que, ao receber 401, dispara um refresh de token e repete a chamada original automaticamente. Limite a uma retentativa para evitar loop infinito caso a credencial esteja realmente inválida.

403 Forbidden: permissão e WAF

O 403 significa que você está autenticado, mas não pode realizar aquela operação. Há dois cenários bem diferentes por trás do mesmo código:

Permissão / escopo insuficiente

Seu client foi autenticado, mas não tem o escopo necessário para o endpoint, ou está tentando acessar um recurso que pertence a outro tenant. A correção é de configuração: ajustar os escopos do client ou confirmar que o ID do recurso pertence à sua conta.

WAF bloqueando webhook com IP privado

Este é um 403 que confunde muita gente. Ao chamar POST /v1/webhooks com uma URL apontando para localhost ou um endereço de IP privado, a requisição é bloqueada pelo WAF na borda CloudFront e retorna 403 Forbidden — mesmo com token perfeitamente válido. É uma proteção contra SSRF e contra cadastrar endpoints que nunca receberiam a entrega.

# ERRADO — 403 do WAF, URL privada curl -X POST https://api-hml.signdocs.com.br/v1/webhooks \ -H "Authorization: Bearer $TOKEN" \ -d '{"url": "http://localhost:3000/hook"}' # ✗ bloqueado # CORRETO — use um túnel HTTPS público para testar curl -X POST https://api-hml.signdocs.com.br/v1/webhooks \ -H "Authorization: Bearer $TOKEN" \ -d '{"url": "https://seu-tunel.ngrok.io/hook"}' # ✓

Para testar webhooks localmente, exponha seu endpoint via um túnel HTTPS público (ngrok e similares) ou use um serviço como webhook.site. A mecânica de entrega, retry e verificação está detalhada no guia de webhooks e eventos da API de assinatura.

404 Not Found: recurso inexistente

O 404 aparece quando o recurso referenciado não existe. As causas típicas, em ordem de frequência:

  • ID incorreto: um typo no transaction_id ou session_id.
  • Ambiente trocado: você criou o recurso em homologação e está consultando em produção (ou vice-versa). Os ambientes são isolados; um ID de HML não existe em prod.
  • Recurso expirado em homologação: no ambiente de homologação, as entidades têm TTL de 7 dias. Uma sessão criada na semana passada já não existe.

Antes de tratar um 404 como erro permanente, confirme o ambiente. O host de homologação é api-hml.signdocs.com.br (com hífen, não api.hml...). Detalhes sobre o sandbox e seu ciclo de vida estão no guia de ambiente de homologação e sandbox da API.

409 Conflict: concorrência e TransactionConflict

O 409 sinaliza um conflito de estado. Há dois sabores, e a ação correta depende de qual deles você recebeu:

TransactionConflict (transitório, repita)

Quando duas operações concorrem pelo mesmo recurso — por exemplo, criar várias sessões em paralelo dentro de um mesmo envelope, colidindo em um contador compartilhado — o resultado é um TransactionConflict. Este é um erro transitório e seguro de repetir: aplique um retry curto com backoff exponencial e jitter, de 2 a 3 tentativas. Combinado com uma chave de idempotência, o retry não corre risco de aplicar a operação duas vezes.

Conflito de estado (determinístico, não repita)

Tentar assinar uma transação já concluída, cancelar uma já cancelada ou modificar um envelope finalizado também gera 409 — mas aqui repetir não adianta, porque o estado do recurso é incompatível com a operação. Leia o estado atual e ajuste o fluxo.

Idempotência salva vidas: envie uma chave de idempotência (header X-Idempotency-Key) nas operações de criação. Assim, se um 409 ou um timeout deixar você em dúvida sobre se a operação foi aplicada, repetir com a mesma chave é seguro — a API reconhece a chave e não duplica o recurso. Veja a mecânica completa (retenção de 24h, conflito de corpo) em rate limits, paginação e idempotência na API.

422 Unprocessable Entity: semântica inválida

Enquanto o 400 trata de sintaxe (o JSON está malformado, o tipo está errado), o 422 indica que a requisição é sintaticamente válida, mas semanticamente impossível de processar segundo as regras de negócio. Exemplos no contexto de assinatura:

  • Um signatário sem os dados exigidos pelo perfil (por exemplo, perfil com OTP sem e-mail nem telefone para receber o código).
  • Um signatário sem CPF nem CNPJ, quando ao menos um deles é obrigatório.
  • Um signerIndex fora do intervalo declarado em totalSigners ao adicionar uma sessão ao envelope.

O corpo de erro de um 422 quase sempre traz details apontando exatamente qual regra foi violada. Diferentemente de um 5xx, repetir não ajuda: corrija a lógica de montagem do payload.

429 Too Many Requests: rate limiting

O 429 indica que você atingiu o limite de requisições da sua janela. A API devolve os cabeçalhos padronizados RateLimit-Limit, RateLimit-Remaining e RateLimit-Reset, além de Retry-After com os segundos a esperar. Este é o erro de retry mais "educado" que existe — a própria resposta te diz quanto esperar.

// HTTP/1.1 429 Too Many Requests // Content-Type: application/problem+json // RateLimit-Limit: 1000 // RateLimit-Remaining: 0 // RateLimit-Reset: 3600 // Retry-After: 3600 { "type": "https://api.signdocs.com.br/errors/too-many-requests", "title": "Too Many Requests", "status": 429, "detail": "Rate limit exceeded" }

A regra é simples: ao receber 429, respeite o Retry-After e repita após esse intervalo. Para evitar 429 em primeiro lugar, prefira webhooks a polling, agregue chamadas e distribua picos. A política completa de cotas e cabeçalhos está em rate limits, paginação e idempotência na API.

5xx: erros de servidor e transitórios

Os códigos 500, 502, 503 e 504 indicam que a falha está do lado do servidor ou da infraestrutura — não há nada errado com sua requisição. São, por natureza, transitórios: um deploy em andamento, um cold start, uma sobrecarga momentânea.

  • 500 Internal Server Error: exceção inesperada no servidor. Repita com backoff; se persistir, registre o seu x-correlation-id, o horário em UTC e o endpoint, e contate o suporte.
  • 502 Bad Gateway / 503 Service Unavailable: indisponibilidade temporária. Repita após um intervalo crescente.
  • 504 Gateway Timeout: a operação demorou demais. Cuidado: a operação pode ter sido aplicada mesmo com timeout — por isso, repita com idempotência.

Para qualquer 5xx persistente, verifique a página pública de status da API antes de abrir um chamado: pode haver um incidente já em andamento.

Estratégia de retry: o que repetir e como

A pergunta central do tratamento de falhas é: este erro deve ser repetido? A resposta depende inteiramente do código.

Categoria Códigos Repetir? Como
Rate limit 429 Sim Respeitar Retry-After
Servidor 500, 502, 503, 504 Sim Backoff exponencial + jitter, com idempotência
Timeout de rede 408 / sem resposta Sim Backoff + idempotência
Conflito concorrente 409 TransactionConflict Sim (curto) 2–3 tentativas, backoff curto
Autenticação 401 Sim (uma vez) Renovar token e repetir uma vez
Validação / permissão / estado 400, 403, 404, 422, 409 (estado) Não Corrigir antes de repetir

Backoff exponencial com jitter

Ao repetir, nunca use um intervalo fixo — vários clientes repetindo no mesmo instante criam um "trovão" que agrava a sobrecarga (o problema do thundering herd). A solução é o backoff exponencial com jitter: o intervalo cresce a cada tentativa e recebe uma variação aleatória.

# Sequência típica de espera (segundos), com jitter tentativa 1 → 0 (imediata) tentativa 2 → ~1s (2^0 + jitter) tentativa 3 → ~2s (2^1 + jitter) tentativa 4 → ~4s (2^2 + jitter) tentativa 5 → ~8s (2^3 + jitter) # máximo de 5 tentativas; depois, falha definitiva → log + alerta

Um handler de erros completo

Reunindo tudo, abaixo está um handler em TypeScript/Node que classifica o erro pelo status e pelo corpo problem+json, renova token em 401, respeita Retry-After em 429, faz backoff em 5xx/409-conflito e desiste imediatamente nos 4xx determinísticos — sempre registrando o x-correlation-id enviado.

const MAX_RETRIES = 5; // Decide se o erro é elegível para retry function isRetryable(status, problem) { if (status === 429) return true; // rate limit if (status >= 500) return true; // 5xx servidor // 409 transitório: conflito de escrita concorrente (detail indica conflito // de transação); 409 de estado (recurso finalizado/cancelado) não se repete if (status === 409 && /conflict/i.test(problem?.detail ?? '')) return true; return false; // 400/401/403/404/422... } function backoffMs(attempt, retryAfter) { if (retryAfter) return Number(retryAfter) * 1000; // honra Retry-After const base = Math.pow(2, attempt) * 500; // exponencial const jitter = Math.random() * 300; // jitter aleatório return base + jitter; } async function callSignDocs(doRequest, refreshToken) { let tokenRefreshed = false; for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) { const res = await doRequest(); if (res.ok) return await res.json(); const problem = await res.json().catch(() => ({})); // SEMPRE registre o problem + o correlation id que VOCÊ enviou console.error( `[SignDocs] ${res.status} ${problem.title} ${problem.type} corr=${correlationId}`, problem.detail, ); // 401: renova token uma única vez e repete if (res.status === 401 && !tokenRefreshed) { await refreshToken(); tokenRefreshed = true; continue; } // Erros determinísticos: falha imediata, não repete if (!isRetryable(res.status, problem)) { throw new Error(`Erro não recuperável ${res.status} (${problem.title}): ${problem.detail}`); } // Retryable: espera com backoff e tenta de novo if (attempt < MAX_RETRIES) { const wait = backoffMs(attempt, res.headers.get('retry-after')); await new Promise(r => setTimeout(r, wait)); } } throw new Error('Falha após todas as retentativas'); }

Três pontos sobre esse handler: ele limita a renovação de token a uma vez (evitando loop com credencial inválida); envia um X-Idempotency-Key nas operações de criação dentro de doRequest — assim, os retries de 5xx e 409 não duplicam recursos; e gera um x-correlation-id por operação lógica, registrado em cada log. Para o fluxo completo em REST puro, consulte o quickstart de integração com cURL e REST — e os SDKs oficiais já embutem retry e backoff.

Logging e observabilidade

Tratamento de erro sem observabilidade é tratamento cego. Em produção, você precisa conseguir responder rapidamente "por que aquela assinatura falhou?" — e isso depende de logs estruturados desde o primeiro dia.

O que registrar em cada falha

  • x-correlation-id: o item não negociável. Gere um UUID por operação, envie-o como cabeçalho em cada chamada e registre-o — a API o aceita e ele é a chave para correlacionar a sua chamada com os logs do lado do SignDocs.
  • Status HTTP e type/title: para agregar métricas (quantos 429 por hora? quantos 401?).
  • Endpoint e método: para saber qual operação falhou.
  • Timestamp em UTC: facilita a correlação com os logs do lado da API.
  • Número de retentativas: revela problemas crônicos de concorrência ou de rede.

Métricas e alertas

Acima do log individual, configure métricas agregadas: taxa de erro por código, latência por endpoint e contagem de eventos enviados para a dead-letter queue de webhooks. Defina alertas para anomalias — um pico súbito de 401, por exemplo, costuma indicar uma credencial revogada ou um relógio dessincronizado afetando a validação de token.

Ao abrir um chamado de suporte, inclua sempre: o seu x-correlation-id, o endpoint (instance do erro), o horário aproximado em UTC e o código HTTP recebido. Esse conjunto reduz drasticamente o tempo de diagnóstico, porque permite à equipe localizar exatamente sua requisição nos logs internos.

Checklist de depuração

Quando algo falha e você não sabe por onde começar, percorra esta lista na ordem:

  1. Leia o status, o title e o detail — eles dizem a categoria e a causa específica do problema.
  2. Confira o ambiente — você está em api-hml.signdocs.com.br (homologação) ou em produção? IDs não cruzam ambientes.
  3. O token está válido? Um 401 quase sempre é token expirado — renove e repita.
  4. O profile está correto? Em 400, verifique se não enviou DIGITAL_SIGN_A1 como profile.
  5. A URL do webhook é pública e HTTPS? Um 403 ao cadastrar webhook geralmente é o WAF rejeitando IP privado.
  6. O recurso ainda existe? Em homologação, lembre-se do TTL de 7 dias.
  7. Guarde o x-correlation-id (e o horário UTC) de qualquer falha que você não conseguir resolver e leve-os ao suporte.

Perguntas Frequentes

O que significa o erro 401 na API de assinatura?

O HTTP 401 Unauthorized indica que o token de acesso está ausente, malformado ou expirado. A causa mais comum é o uso de um access token que já passou da validade — tokens emitidos pelo fluxo OAuth2 client-credentials têm vida curta. A solução é solicitar um novo token ao endpoint de autenticação e repetir a requisição. Não confunda 401 com 403: 401 é problema de autenticação (quem é você), 403 é problema de permissão (você não pode fazer isso).

Por que recebo erro 400 ao enviar DIGITAL_SIGN_A1 como profile?

Porque DIGITAL_SIGN_A1 é um valor de step.type, nunca de policy.profile. Ao criar uma signing session, o campo profile aceita valores como DIGITAL_CERTIFICATE para assinatura com certificado ICP-Brasil. Enviar DIGITAL_SIGN_A1 nesse campo retorna 400 Bad Request com erro de validação. O tipo DIGITAL_SIGN_A1 só aparece como step.type na resposta da API, descrevendo a etapa de assinatura digital dentro do fluxo.

O que causa o erro 403 em webhooks com URL de IP privado?

Ao cadastrar um webhook apontando para localhost ou um endereço de IP privado, a requisição POST /v1/webhooks é bloqueada pelo WAF (Web Application Firewall) na borda CloudFront, retornando 403 Forbidden. Isso é uma proteção contra SSRF e contra entregas que nunca chegariam ao destino. Para testar webhooks localmente, use um túnel HTTPS público (como ngrok) ou um serviço como webhook.site, e em produção exponha um endpoint HTTPS acessível pela internet.

Devo fazer retry de todos os erros da API de assinatura?

Não. Apenas erros transitórios devem ser repetidos: 429 (rate limit, respeitando o header Retry-After), 408 (timeout) e 5xx (erros de servidor como 500, 502, 503, 504). Erros 4xx de validação como 400, 401, 403, 404, 409 e 422 são determinísticos — repetir a mesma requisição produzirá o mesmo erro. Para esses, corrija o payload, o token ou a permissão antes de tentar novamente. Sempre use backoff exponencial com jitter nos retries automáticos.

Como tratar o erro 409 TransactionConflict ao criar sessões de assinatura?

O HTTP 409 Conflict (TransactionConflict) ocorre quando duas operações concorrem por um mesmo recurso, por exemplo ao criar sessões em paralelo dentro de um mesmo envelope, colidindo em um contador compartilhado. É um erro transitório e seguro de repetir: aplique um retry curto com backoff exponencial e jitter, idealmente de 2 a 3 tentativas. Combinar o retry com uma chave de idempotência evita que a operação seja aplicada duas vezes caso a primeira tentativa tenha de fato sido persistida.

Qual a estrutura do corpo de erro retornado pela API?

A API segue a RFC 7807 (Problem Details): as falhas retornam Content-Type application/problem+json com os campos type (URI estável da categoria do erro, ex.: https://api.signdocs.com.br/errors/bad-request), title (nome curto da categoria), status (o código HTTP), detail (a explicação específica, como o campo inválido e o valor aceito) e instance (o caminho da requisição). Trate os erros pelo status e pelo type; use o detail para logs e mensagens ao operador.

Como correlaciono uma falha com os logs do SignDocs ao abrir um chamado?

Envie um cabeçalho x-correlation-id próprio (um UUID por requisição) em todas as chamadas — a API o aceita e ele vincula os seus logs aos do lado do servidor. Ao abrir um chamado, inclua esse correlation id, o endpoint (o campo instance do erro RFC 7807), o horário aproximado em UTC e o código HTTP recebido — isso reduz drasticamente o tempo de diagnóstico.

Construa integrações resilientes com a API SignDocs

Erros RFC 7807 estruturados, cabeçalhos de rate limit padronizados, idempotência por X-Idempotency-Key e webhooks com retry automático: a API SignDocs foi desenhada para que sua integração lide bem com o caminho infeliz. O acesso é um plano sob medida, com sandbox de homologação gratuito para exercitar cada cenário de falha antes de produção.

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