Dois tokens, dois públicos: Bearer do integrador e token de embed do signatário

Quase toda API tem uma credencial só: o seu token, em toda chamada. A API de assinatura tem duas, e a diferença não é organizacional — é de público. O seu token de aplicação representa a empresa que pediu a assinatura; o token de embed representa a pessoa que vai assinar. São interesses distintos e, em alguns momentos, opostos: o remetente não deveria conseguir avançar as etapas em nome de quem assina. Daí a consequência que aparece como bug: algumas rotas recusam o seu Bearer com 401, e o erro é indistinguível de credencial errada. Este guia traz o mapa completo e o que fazer quando um link de assinatura já foi consumido.

Se você chegou aqui por um 401: confira se a rota é uma das seis que só aceitam token de embed. Elas são as que o signatário usa — carregar a sessão, avançar etapas, reenviar código — e recusam o token de aplicação por desenho, não por defeito.

Duas credenciais, dois interesses

O seu token de aplicação, obtido em POST /oauth2/token, representa a empresa que pediu a assinatura. Ele vale para o tenant inteiro, atravessa todas as transações e vive 15 minutos — independentemente do que acontece com qualquer sessão específica.

O token de embed representa a pessoa que vai assinar. Ele nasce preso a uma sessão, morre com ela e não serve para mais nada.

A separação existe porque os dois papéis não são a mesma pessoa e, em alguns momentos, têm interesses opostos. Se o token de aplicação pudesse avançar as etapas de uma sessão, quem enviou o documento poderia concluir a assinatura em nome de quem deveria assinar — e a evidência registraria, com todos os carimbos de tempo em ordem, um ato que a pessoa nunca praticou. Restringir as rotas de avanço ao token do signatário é o que mantém essa fronteira de pé.

Dito de outro modo: o 401 que incomoda é a fronteira funcionando.

O mapa das seis rotas

Rota Quem autentica Quem usa na prática
GET /v1/signing-sessions/{id} Token de embed A página de assinatura, ao carregar
POST /v1/signing-sessions/{id}/advance Token de embed A página, a cada etapa concluída
POST /v1/signing-sessions/{id}/resend-otp Token de embed O signatário, ao pedir outro código
GET /v1/trust-sessions/{id} Token de embed A página de autenticação do ato
POST /v1/trust-sessions/{id}/advance Token de embed A página, a cada etapa
POST /v1/trust-sessions/{id}/resend-otp Token de embed A pessoa autenticando

E as rotas vizinhas, que usam o seu Bearer normalmente:

Rota Escopo Para quê
POST /v1/signing-sessions transactions:write Criar a sessão
GET /v1/signing-sessions/{id}/status transactions:read Consultar o andamento
POST /v1/signing-sessions/{id}/cancel transactions:write Cancelar
POST /v1/signing-sessions/{id}/link transactions:write Emitir um link novo

A confusão mora nestas duas linhas: GET /v1/signing-sessions/{id} e GET /v1/signing-sessions/{id}/status. A primeira parece a rota óbvia para "consultar a sessão" — é o padrão REST que todo mundo espera — mas ela é a que a página de assinatura usa para carregar o estado, e por isso só aceita token de embed. A rota do integrador é /status. Um snippet que chama a primeira com Bearer devolve 401 e não é bug.

De onde vem o token de embed

Ele começa como o clientSecret devolvido na criação da sessão:

POST /v1/signing-sessions Authorization: Bearer <access_token> // seu token, aqui sim // 201 { "sessionId": "ss_01J...", "transactionId": "tx_01J...", "url": "https://sign-hml.signdocs.com.br/s/ss_01J...", "clientSecret": "ss_secret_a3f0...", // a credencial do signatário "expiresAt": "2026-09-08T10:14:02Z" }

O link entregue à pessoa é a url com o segredo anexado como parâmetro cs. A página de assinatura lê esse parâmetro, obtém o token de embed e passa a usá-lo nas chamadas de advance.

Na prática, você raramente manipula esse token: entrega o link e acompanha por /status ou por webhook. Ele só aparece explicitamente em dois casos — integração headless, que conduz as etapas por conta própria, e modo embarcado, em que o segredo é passado ao componente de checkout.

Uma consequência que vale registrar: o clientSecret é uma credencial, ainda que de vida curta e escopo mínimo. Ele não deveria aparecer em log de aplicação, em URL registrada por um proxy, nem em mensagem encaminhável para quem não é o signatário.

Uso único: o link que já foi usado

O link de assinatura vale uma vez. Depois que a pessoa conclui — ou que o token embutido é consumido de outra forma —, reabrir a mesma URL devolve:

401 Embed token has been consumed

Isso costuma soar como problema até se pensar na alternativa: um link reutilizável seria uma credencial permanente circulando por e-mail, capaz de reabrir a sessão de outra pessoa. O uso único é o que impede isso.

O efeito colateral é real, porém. A pessoa fecha a aba sem querer, o e-mail é encaminhado e alguém abre antes, o aplicativo de mensagens pré-carrega o link. Nada disso deveria custar uma transação nova — e não custa:

POST https://api-hml.signdocs.com.br/v1/signing-sessions/{sessionId}/link Authorization: Bearer <access_token> // transactions:write // 200 { "sessionId": "ss_01J...", "transactionId": "tx_01J...", "url": "https://sign-hml.signdocs.com.br/s/ss_01J...?cs=...", "expiresAt": "2026-09-08T10:14:02Z", "expiresIn": 244800 }

Três propriedades desta rota, cada uma respondendo a uma pergunta previsível:

  • Não consome cota. Reemitir um link não é enviar um documento novo, e o plano não é debitado outra vez.
  • Não estende o prazo. O expiresAt devolvido é o da sessão original. Relinkar não é uma forma de esticar o prazo indefinidamente — se a sessão venceu, o caminho é outro.
  • Só em sessões ACTIVE. Uma sessão concluída ou cancelada responde 409 Session cannot be linked in status: <status>. Faz sentido: um link para uma sessão encerrada não autenticaria coisa alguma.

Vale para sessões avulsas e para sessões dentro de um envelope, indistintamente.

O que fazer quando a sessão já terminou

Se o pedido é "o cliente quer ver o que assinou", relinkar é a ferramenta errada. O que ele quer é o documento:

Situação Rota
Sessão ativa, link perdido ou consumido POST /v1/signing-sessions/{id}/link
Envelope concluído, quer o PDF final POST /v1/envelopes/{id}/combined-stamp
Transação avulsa concluída, quer o PDF GET /v1/transactions/{id}/download
Quer a prova, não o documento GET /v1/transactions/{id}/evidence
Sessão expirada de vez Criar nova — e aí sim consome cota

Depurando um 401

  1. A rota está na lista das seis? Se sim, ela nunca vai aceitar o seu Bearer. Para consultar, troque GET /{id} por GET /{id}/status.
  2. A mensagem diz Embed token has been consumed? O link foi usado. Emita outro com /link, sem custo.
  3. É uma rota de Bearer mesmo assim? Verifique a validade do token — ele dura 900 segundos e integrações que o guardam entre execuções longas o usam vencido.
  4. É 403, e não 401? Então a credencial foi aceita e o problema é de permissão: consulte o mapa de escopos, onde um escopo inválido descartado em silêncio é a causa mais comum.

Para os prazos que governam cada token e cada sessão, veja todos os prazos da API em uma página. Para o fluxo embarcado, em que o segredo é passado ao componente de checkout, assinatura incorporada. E para o runbook de sessões vencidas, sessão expirada: reenviar, cancelar ou recriar.

Perguntas Frequentes

Quais rotas não aceitam o meu token Bearer?

Seis, e todas são rotas que a pessoa que assina usa: GET /v1/signing-sessions/{id}, POST /v1/signing-sessions/{id}/advance, POST /v1/signing-sessions/{id}/resend-otp e as três equivalentes em /v1/trust-sessions. Todas as demais rotas da API usam o Bearer normalmente.

Como consulto o andamento de uma sessão, então?

Com GET /v1/signing-sessions/{id}/status, que é rota de Bearer e exige transactions:read. A confusão é natural: GET /v1/signing-sessions/{id} parece a rota óbvia para consultar, mas ela é a que a página de assinatura usa para carregar a sessão — e por isso só aceita o token de embed.

De onde vem o token de embed?

Do clientSecret devolvido na criação da sessão. Ele é anexado à URL de assinatura como parâmetro, e a página de assinatura o troca pelo token que usa nas chamadas seguintes. Na prática, você raramente o manipula: entrega o link e a página cuida do resto.

Por que essa separação existe?

Porque o token de aplicação vale para o tenant inteiro e não expira junto com a sessão. Se ele pudesse avançar etapas, quem enviou o documento poderia concluir a assinatura em nome do signatário — e a evidência registraria um ato que a pessoa não praticou. Restringir as rotas de avanço ao token do signatário é o que mantém a separação entre quem pede e quem assina.

O link de assinatura pode ser reutilizado?

Não: ele é de uso único. Depois que o signatário conclui, ou que o token embutido é consumido de outra forma, reabrir a mesma URL devolve 401 Embed token has been consumed. É o comportamento pretendido — um link permanente seria uma credencial permanente circulando por e-mail.

Como emito um link novo sem recriar a transação?

Com POST /v1/signing-sessions/{id}/link, que gera uma URL nova para a sessão existente, sem consumir cota. Vale para sessões avulsas e de envelope. Duas condições: a sessão precisa estar ACTIVE — uma concluída ou cancelada devolve 409 — e o prazo original não é estendido, porque o link herda o expiresAt da sessão.

E se a sessão já estiver concluída?

Aí não há o que relinkar: um link para uma sessão encerrada não autenticaria nada. Para dar acesso ao documento assinado, use POST /v1/envelopes/{id}/combined-stamp no caso de envelope, ou GET /v1/transactions/{id}/download no caso de transação avulsa.

Entenda quem autentica o quê antes de depurar um 401

Duas credenciais, dois públicos e um mapa de rotas que evita a tarde perdida. Teste os dois fluxos no sandbox gratuito.

Criar credenciais de homologação Fale com o time comercial