OTP na Assinatura: E-mail, SMS e a Escolha do Canal pelo Signatário via API

O código de uso único (OTP) é o degrau mais usado acima do clique simples: ele prova que quem está assinando tem acesso a um canal que pertence ao signatário, seja a caixa de e-mail, seja o número de celular. Este guia explica como a API da SignDocs Brasil entrega o OTP, quais perfis o exigem, como deixar o signatário escolher o canal na própria página de assinatura com otpChannelSelectable, o que acontece no reenvio e o que fica registrado como evidência.

Tudo o que está descrito aqui é o comportamento atual do fluxo hospedado e dos endpoints, incluindo os limites que você vai encontrar em produção: validade de 10 minutos por código e intervalo mínimo de 60 segundos entre reenvios. Se você ainda está escolhendo o nível de autenticação para cada tipo de documento, comece pelo panorama em autenticação multimétodo e volte aqui para os detalhes do OTP.

Quais perfis usam OTP

O OTP não é um recurso avulso: ele é uma etapa que alguns perfis de política (policy.profile) incluem na sequência de assinatura. Dois perfis o exigem:

Perfil Etapas executadas O que o OTP adiciona
CLICK_PLUS_OTP Aceite por clique, envio do código, verificação do código Posse do canal (e-mail ou celular) além do consentimento
BIOMETRIC_PLUS_OTP Prova de vida, comparação facial, envio do código, verificação do código Posse do canal somada à verificação de que há uma pessoa real com aquele rosto

Nos demais perfis (CLICK_ONLY, BIOMETRIC, BIOMETRIC_DOCUMENT_FALLBACK, BIOMETRIC_SERPRO, BIOMETRIC_SERPRO_AUTO_FALLBACK, DIGITAL_CERTIFICATE) não há etapa de código. A escolha é por signatário, no momento da criação da sessão ou de cada sessão de um envelope, então o mesmo documento pode ter um signatário com OTP e outro com certificado digital.

Como o código é entregue

Quando o signatário chega à etapa de OTP na página hospedada, a plataforma gera um código e o envia por um dos dois canais suportados:

  • E-mail, para o endereço informado em signer.email na criação da sessão. É o canal padrão e está sempre disponível quando existe e-mail.
  • SMS, para o telefone informado no cadastro do signatário, quando habilitado no contrato. O SMS depende de duas condições: o signatário ter um número de celular registrado na sessão e o envio por SMS estar ativado para a sua credencial. Sem uma das duas, o canal simplesmente não aparece.

WhatsApp não é canal de OTP. O que muitas integrações fazem é entregar o link de assinatura pelo WhatsApp e deixar o OTP correr por e-mail ou SMS; são coisas diferentes, e a segunda é a que conta como fator de autenticação registrado.

Validade e reenvio. Cada código vale por 10 minutos. O signatário pode pedir um novo código na própria página, mas o reenvio respeita um intervalo mínimo de 60 segundos: uma solicitação antes disso recebe 409 com a mensagem "Aguarde Ns antes de reenviar", em que N é o tempo restante. Esses limites são fixos da plataforma e não precisam de configuração.

Deixando o signatário escolher o canal

Por padrão, a plataforma escolhe o canal com base no que existe no cadastro do signatário. Há casos, porém, em que quem sabe qual canal funciona melhor é a própria pessoa: o e-mail corporativo com filtro agressivo de spam, o celular sem sinal no interior, o número antigo que ainda consta no cadastro. Para isso existe o campo signer.otpChannelSelectable.

Quando ele é true na criação da sessão, a página hospedada exibe um seletor de canal antes de enviar o código. O seletor só aparece se houver pelo menos dois canais disponíveis para aquele signatário: e-mail (quando há e-mail) e SMS (quando há telefone e o SMS está habilitado no contrato). Com um único canal disponível, o campo não tem efeito e o código segue pelo canal existente.

// POST /v1/signing-sessions — signatário escolhe entre e-mail e SMS { "purpose": "DOCUMENT_SIGNATURE", "policy": { "profile": "CLICK_PLUS_OTP" }, "signer": { "name": "Ana Souza", "email": "ana@exemplo.com.br", "cpf": "12345678901", "otpChannelSelectable": true }, "document": { "content": "JVBERi0xLjQK...", "filename": "contrato-prestacao-servicos.pdf" }, "returnUrl": "https://app.exemplo.com.br/contratos/8841/assinado" }

A resposta é a mesma de qualquer sessão: sessionId, transactionId, status: "ACTIVE", a url da página hospedada, o clientSecret e o expiresAt. O link entregue ao signatário continua sendo url?cs=clientSecret. O que muda é só a experiência na página: a pessoa vê "receber o código por e-mail" ou "por SMS", escolhe, e o código sai pelo canal escolhido.

Trocando de canal no reenvio

A escolha não é definitiva. Se o código não chegou pelo e-mail, o signatário pode pedir o reenvio e, no mesmo gesto, mudar para o SMS (ou o contrário). A troca no reenvio só é aceita quando a sessão foi criada com otpChannelSelectable: true; em sessões sem esse campo, o reenvio repete o canal original. O intervalo de 60 segundos vale igualmente para a troca.

Em envelopes, o campo vai no corpo de cada POST /v1/envelopes/{id}/sessions, dentro de signer, junto do signerIndex. Isso permite, por exemplo, oferecer a escolha ao cliente final e fixar o e-mail para o representante da sua empresa.

O fluxo completo, do ponto de vista do signatário

Vale percorrer o caminho inteiro uma vez, porque é ele que o seu suporte vai precisar explicar quando alguém ligar dizendo que "o código não veio". Com CLICK_PLUS_OTP e o seletor ativo, a sequência na página hospedada é esta:

  1. Abertura do link. O signatário abre url?cs=clientSecret no navegador do celular ou do computador. Não há conta nem senha: o link é a porta de entrada, e é por isso que ele deve ser entregue por um canal em que a pessoa já esteja identificada.
  2. Confirmação de identidade declarada. A página mostra o nome do signatário e pede a confirmação de que é ele. Em seguida vem o aceite dos termos de uso do signatário e do tratamento de dados.
  3. Leitura e aceite do documento. O PDF é exibido; o signatário rola até o fim e clica em concordar. Esse é o consentimento em si, registrado com data, hora, IP e dispositivo.
  4. Escolha do canal. Com o seletor ativo e dois canais disponíveis, a página pergunta por onde enviar o código. Sem seletor, o envio vai direto pelo canal existente.
  5. Código e verificação. O signatário digita o código de uso único recebido. Acertou dentro dos 10 minutos: a etapa fecha e a assinatura é concluída. Errou ou o código expirou: pede outro, respeitando o intervalo de 60 segundos, podendo trocar o canal.
  6. Conclusão. A plataforma finaliza a transação, dispara SIGNING_SESSION.COMPLETED para o seu webhook e, se houver returnUrl, redireciona o signatário para lá com session_id anexado. O redirecionamento é conveniência de navegação; a prova de que a assinatura aconteceu é o webhook ou a consulta em GET /v1/transactions/{id}.

Para o seu sistema, o que importa desse roteiro é que o OTP acontece depois do aceite do documento e antes da conclusão. Um signatário que abriu o link, leu e concordou, mas não completou o código, deixa a sessão em ACTIVE e a transação em andamento; nada foi assinado ainda, e o webhook de conclusão não é disparado. É nesse ponto que reenviar o convite (POST /v1/signing-sessions/{id}/resend-invite) faz sentido, se a pessoa perdeu o link.

Quando faz sentido ligar o seletor

O seletor é uma decisão de conversão, não de segurança: os dois canais têm o mesmo peso como evidência de posse. Ligue quando:

  • O público assina majoritariamente pelo celular e o e-mail é secundário (vendas B2C, serviços, locação).
  • Você já registra telefone e e-mail no cadastro e não quer apostar em qual está atualizado.
  • Há histórico de códigos "que não chegaram" e o suporte gasta tempo com isso.

Mantenha desligado quando o seu processo exige que o código vá por um canal específico por política interna (por exemplo, sempre o e-mail corporativo registrado no contrato) ou quando você mesmo informa apenas um canal e prefere que a página não ofereça uma escolha que não existe.

O que fica registrado como evidência

A etapa de OTP grava, na trilha de auditoria da transação, o que um terceiro precisaria para reconstruir o que aconteceu:

Registro Conteúdo
Início da etapa Evento STEP.STARTED da etapa de envio do código, com data e hora em UTC
Canal utilizado E-mail ou SMS, incluindo o canal escolhido quando o seletor estava ativo
Verificação Resultado da conferência do código (STEP.COMPLETED ou STEP.FAILED), com data e hora
Contexto do signatário IP, geolocalização, user-agent e carimbos de data e hora em UTC, como em toda etapa

O código em si não é evidência e não aparece na trilha depois de usado; o que vale é o par "código enviado para este canal, código correto informado neste instante, a partir deste dispositivo". Ao final da assinatura, tudo isso compõe o pacote de evidências .p7m da transação (GET /v1/transactions/{id}/evidence), documentado em evidence pack .p7m. Os eventos STEP.* chegam ao seu webhook se você os assinou; o catálogo está em webhooks e eventos.

Tratando os erros mais comuns

  • 409 no reenvio. É o intervalo de 60 segundos. A página hospedada já mostra a contagem; se a sua integração dispara reenvios pelo próprio fluxo, respeite a mensagem "Aguarde Ns" em vez de repetir a chamada.
  • Código expirado. Passados 10 minutos, o código informado é rejeitado. O signatário pede um novo; a sessão continua ativa até o expiresAt (72 horas por padrão), então expirar um código não invalida o link.
  • Seletor não aparece. Quase sempre é um canal só disponível: falta o telefone no cadastro ou o SMS não está habilitado no contrato. Confira os dois antes de abrir chamado.
  • E-mail "não chegou". Verifique a caixa de spam e o endereço em signer.email. Se o seletor estiver ativo, a troca para SMS resolve na hora, sem nova sessão.

Os códigos de erro e o formato RFC 7807 das respostas estão em códigos de erro e tratamento de falhas; os tropeços de integração que vemos com mais frequência, em erros comuns.

Quando o OTP não basta

O OTP responde a uma pergunta precisa: quem assinou tinha acesso ao canal do signatário? Ele não responde quem é a pessoa. Um celular clonado por SIM swap ou uma caixa de e-mail invadida recebem o código normalmente, e a evidência apontaria para a vítima. Por isso o OTP é a escolha certa para aceites, termos e contratos de risco moderado, e insuficiente sozinho quando o custo de assinar com a pessoa errada é alto.

Nesses casos, suba um degrau: BIOMETRIC_PLUS_OTP mantém o código e acrescenta prova de vida e comparação facial; os perfis com SERPRO cruzam o rosto com as bases governamentais; DIGITAL_CERTIFICATE usa o certificado ICP-Brasil do signatário (A1 diretamente na página; A3 por meio do assinador SignDocs). A comparação entre todos os degraus, com o que cada um registra, está em autenticação multimétodo.

Testando no sandbox

No ambiente de homologação (api-hml.signdocs.com.br, gratuito e sem cartão), o fluxo de OTP é o mesmo de produção, o que permite validar o seletor de canal, o intervalo de reenvio e a expiração do código com dados de teste. Crie uma sessão CLICK_PLUS_OTP com otpChannelSelectable: true, abra o link, escolha o canal, deixe o código expirar de propósito e confirme que o reenvio antes de 60 segundos devolve 409. Depois assine os eventos STEP.* num webhook de teste e confira o que chega. O roteiro completo de testes está em testar a integração no sandbox e no CI.

Perguntas Frequentes

Quais perfis da API exigem OTP?

Dois: CLICK_PLUS_OTP (aceite por clique seguido do envio e da verificação do código) e BIOMETRIC_PLUS_OTP (prova de vida e comparação facial seguidas do código). Nos demais perfis não há etapa de OTP. O perfil é definido por signatário em policy.profile, na criação da sessão ou de cada sessão do envelope, então um mesmo documento pode combinar um signatário com OTP e outro com certificado digital ou biometria. O panorama de todos os perfis está em autenticação multimétodo.

Por quais canais o código pode ser enviado?

Por e-mail, para o endereço informado em signer.email, e por SMS, para o telefone registrado no cadastro do signatário, quando o envio por SMS está habilitado no contrato. O SMS depende dessas duas condições ao mesmo tempo; faltando uma, o canal não é oferecido. WhatsApp não é canal de OTP: ele pode transportar o link de assinatura, mas o código segue por e-mail ou SMS, e é esse envio que fica registrado como fator de autenticação na trilha de auditoria.

O que faz o campo otpChannelSelectable?

Quando signer.otpChannelSelectable é true na criação da sessão, a página hospedada exibe um seletor para o signatário escolher entre e-mail e SMS antes de receber o código. O seletor só aparece se houver pelo menos dois canais disponíveis para aquela pessoa. A escolha é feita no aceite e pode ser trocada no reenvio, sem criar nova sessão. Em envelopes, o campo vai dentro de signer em cada POST /v1/envelopes/{id}/sessions, o que permite oferecer a escolha a um signatário e fixar o canal para outro.

Por quanto tempo o código vale e com que frequência posso reenviar?

Cada código vale por 10 minutos. O reenvio respeita um intervalo mínimo de 60 segundos: uma solicitação antes disso recebe 409 com a mensagem "Aguarde Ns antes de reenviar". Um código expirado não invalida o link: a sessão continua ativa até o expiresAt (72 horas por padrão) e o signatário simplesmente pede um novo código. Esses limites são fixos da plataforma e valem igualmente em homologação e em produção.

O que fica registrado como evidência da etapa de OTP?

O início da etapa de envio (STEP.STARTED), o canal utilizado (e-mail ou SMS, incluindo o escolhido pelo signatário quando o seletor estava ativo), o resultado da verificação do código (STEP.COMPLETED ou STEP.FAILED) e o contexto do signatário: IP, geolocalização, user-agent e carimbos de data e hora em UTC. O código em si não é armazenado como evidência depois de usado. Tudo isso compõe o pacote de evidências .p7m da transação, disponível em GET /v1/transactions/{id}/evidence.

OTP é suficiente para qualquer documento?

Não. O OTP prova posse de um canal, não a identidade da pessoa: um celular clonado ou um e-mail invadido recebem o código normalmente. Para aceites, termos e contratos de risco moderado ele equilibra bem conversão e evidência. Quando o custo de assinar com a pessoa errada é alto, suba um degrau: BIOMETRIC_PLUS_OTP acrescenta prova de vida e comparação facial, os perfis com SERPRO cruzam o rosto com bases governamentais e DIGITAL_CERTIFICATE usa o certificado ICP-Brasil do signatário (A1 na página; A3 por meio do assinador SignDocs).

Teste o OTP com escolha de canal no sandbox

Crie uma sessão CLICK_PLUS_OTP com otpChannelSelectable no ambiente de homologação gratuito, veja o seletor na página hospedada e confira o que chega no seu webhook. Para SMS em produção e volumes, o time comercial dimensiona o plano sob medida.

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