Pular para o conteúdo

Receber webhooks

Um webhook é o AssinaJá a bater à porta do seu sistema quando alguma coisa acontece a um documento, em vez de o seu sistema andar a perguntar. É um pedido POST que a plataforma faz a um endereço seu.

Isto é matéria para quem integra o AssinaJá com outro programa. Se só quer ver o que se passou com um documento, use o histórico do documento.

O endereço não é livre. A plataforma valida-o antes de o guardar, para não poder ser usada para bater em máquinas que não são suas:

  • HTTPS, sempre.
  • Porta 443 ou 8443, e mais nenhuma.
  • Um anfitrião público: nem localhost, nem *.localhost, nem *.internal, nem metadata.google.internal.
  • Um endereço público. Se o nome resolver para um endereço privado ou reservado — 10.x, 172.16–31.x, 192.168.x, 127.x, 169.254.x, 100.64.x, multicast, ou os equivalentes em IPv6 — é recusado. Todos os endereços a que o nome resolve são verificados, não só o primeiro.

A recusa aparece na sua língua:

Recusa O que fazer
“Indique o endereço do webhook.” o campo está vazio
“O endereço não é um URL válido.” falta o esquema, ou há um erro de escrita
“O endereço tem de usar HTTPS.” troque http:// por https://
“A porta tem de ser 443 ou 8443.” tire a porta do endereço, ou use uma destas
“O anfitrião é reservado (localhost, .internal, metadados de cloud) e não pode receber webhooks.” use um nome alcançável da Internet
“O endereço IP não é público.” escreveu um IP privado directamente
“O nome do anfitrião não resolve para nenhum endereço.” confirme o DNS
“Não foi possível resolver o nome do anfitrião.” o DNS falhou; tente outra vez
“O anfitrião resolve para um endereço que não é público.” o nome aponta para dentro da sua rede

Ao escolher os eventos, “Um dos eventos escolhidos não é reconhecido.” quer dizer que a lista enviada tem um nome que a plataforma não conhece — confirme-o na tabela acima. E “Não há endereço de webhook configurado — indique um ou guarde a subscrição primeiro.” aparece quando se tenta subscrever eventos antes de haver endereço.

Evento Quando é enviado
document.completed Todos assinaram e o documento final está pronto
document.rejected Um signatário recusou
document.canceled O dono cancelou o documento
document.expired O prazo passou sem estar concluído
document.pdf_application_failed Não foi possível aplicar a assinatura ao PDF
recipient.signed Um signatário assinou — uma vez por signatário
document.sent O documento foi publicado e os convites saíram

Os dois últimos são novos e não são finais: o documento continua a andar depois deles. Não trate nenhum evento como “está terminado” — veja o estado do documento que vem no corpo.

O document.pdf_application_failed é o que não se deve ignorar: quer dizer que alguém assinou mas a assinatura não chegou a entrar no ficheiro. No AssinaJá isso aparece como Problema técnico no detalhe do documento, com Tentar de novo — ver Acompanhar e gerir documentos.

O cabeçalho Idempotency-Key serve para o seu lado descartar repetições, mas deixou de ter uma forma só. Depende de para onde vai o evento e de qual é:

Destino Evento Chave
Endereço do documento (webhookUrl) qualquer, menos recipient.signed {documentPublicId}:{event}
Endereço do documento recipient.signed {documentPublicId}:{event}:{recipientId}
Endereço da organização qualquer, menos recipient.signed {documentPublicId}:{event}:org
Endereço da organização recipient.signed {documentPublicId}:{event}:{recipientId}:org

Guarde e compare a chave inteira, como um texto opaco. Não a parta em pedaços. Se tem uma restrição de unicidade em (documento, evento) na sua base de dados, ela passa a recusar entregas legítimas: o recipient.signed chega uma vez por signatário, e um documento com endereço próprio numa organização que também tem endereço configurado produz duas entregas do mesmo evento, com chaves diferentes.

O segredo é o que lhe permite ter a certeza de que o pedido veio mesmo do AssinaJá. Está em Definições → Desenvolvedores, no separador Webhooks, sub-separador Segredo. Só o Criador da organização o vê.

Segredo do webhook:

O segredo é utilizado para verificar a assinatura dos eventos enviados para o seu webhook.

Use Gerar segredo da primeira vez, e Copiar para o guardar. Não deixe essa página sem o ter guardado:

Guarde este valor agora — não volta a ser mostrado.

Rodar gera um segredo novo sem interromper as entregas:

Tem a certeza que pretende rodar o segredo? O segredo anterior será válido por 24 horas.

Durante essas 24 horas — “Segredo anterior válido mais 24 h.” — os pedidos vão assinados com os dois segredos, separados por vírgula no mesmo cabeçalho. É por isso que a verificação tem de aceitar uma lista, e não um valor único. Passado esse prazo, só o novo é usado.

Cada pedido traz estes cabeçalhos:

Cabeçalho O que é
X-AssinaJa-Signature A assinatura, v1=<hex>, ou várias separadas por vírgula durante uma rotação
X-AssinaJa-Event O nome do evento
X-AssinaJa-Delivery Um identificador novo em cada tentativa — não serve para descartar repetições
X-AssinaJa-Timestamp A hora, em segundos Unix
Idempotency-Key Ver o quadro acima — quatro formas possíveis

A assinatura é um HMAC-SHA256 sobre a hora e o corpo do pedido juntos por um ponto — {timestamp}.{corpo} — em hexadecimal minúsculo e prefixado por v1=.

A chave não é o segredo tal como o copiou. O segredo vem codificado em base64url; a chave do HMAC são os bytes que resultam de o descodificar. Passar o texto directamente à função de HMAC produz uma assinatura que nunca bate certo — é a causa mais comum de a verificação falhar.

const crypto = require('crypto');
function verifyWebhookSignature(secret, timestamp, body, signatureHeader) {
const message = `${timestamp}.${body}`;
// o segredo é base64url — a chave são os bytes descodificados, não o texto
const secretBytes = Buffer.from(secret, 'base64url');
const expected = 'v1=' + crypto
.createHmac('sha256', secretBytes)
.update(message)
.digest('hex');
// durante uma rotação vêm as duas assinaturas
const signatures = signatureHeader.split(',').map(s => s.trim());
return signatures.some(sig => sig === expected);
}

Use o corpo tal como chegou, em bruto. Se o passar por um analisador de JSON e voltar a serializá-lo, os bytes mudam e a assinatura deixa de bater certo.

O que responde O que acontece
2xx Entregue. Não há nova tentativa. O corpo da resposta tem de caber em 4 KB.
3xx Volta a tentar — os reencaminhamentos não são seguidos.
4xx Volta a tentar, tal como um 5xx.
5xx Volta a tentar.
Sem resposta em 10 segundos Volta a tentar.

Só uma resposta 2xx conta como entregue. Não existe nenhum código que faça a plataforma desistir de propósito: se quer que uma entrega pare, responda 2xx e deite o conteúdo fora do seu lado.

As tentativas são espaçadas de 1 min, 2 min, 5 min, 15 min, 30 min, 60 min e 60 min. São oito ao todo, e a oitava é uma entrega a sério — só quando ela falha é que a entrega fica marcada como falhada.

O detalhe de cada documento mostra a última entrega desse documento, seja de que evento for, no cartão Integração — com Webhook (o nome do evento), Estado, Tentativas, Entregue em e, se falhou, o Erro. É o sítio mais rápido para distinguir “o AssinaJá não enviou” de “o meu servidor não aceitou”. Não há botão de reenvio neste cartão.

Para ver o histórico completo da organização e reenviar entregas falhadas, vá a Definições → Desenvolvedores → Webhooks, ao sub-separador Entregas:

Cada evento enviado, com a resposta do seu endereço. As entregas falhadas podem ser reenviadas.

Só o Criador da organização tem acesso a esse ecrã.

Quando o reenvio não pega, o ecrã diz porquê:

Essa entrega já não existe.

Essa entrega já foi recebida — só as falhadas se reenviam.

Não é possível reenviar esta entrega neste momento.