Pular para o conteúdo principal

Webhooks

Webhooks são a forma do seu sistema saber sozinho que algo aconteceu com um documento — sem precisar ficar chamando GET /v1/documents/{id} de tempos em tempos. O Firmou avisa o seu endereço a cada evento relevante da cerimônia.

Não existe endpoint na API para cadastrar um webhook — cadastro é ato de gente autenticada por sessão, dentro do Firmou, pela mesma razão de tokens não se emitirem sozinhos: veja Autenticação.

Cadastrando um destino​

  1. Entre no Firmou e vá em Configurações › Webhooks.
  2. Clique em Novo destino e informe a URL do seu endereço. Precisa ser pública — endereços internos (localhost, IP privado, nomes sem domínio) são recusados, e em produção a URL precisa ser https://.
  3. Marque os eventos que interessam. Não existe "quero tudo": um endpoint que recebe qualquer evento fica refém de todo evento novo que o Firmou vier a criar.
  4. Escolha o workspace, se quiser limitar o alcance. Deixando em "Todos os workspaces", o destino recebe aviso de qualquer documento do cliente; escolhendo um, recebe só dos documentos criados nele. É o que separa o ERP que cuida de contrato do sistema de pessoal que cuida de admissão — sem isso, cada um receberia o documento do outro, com nome, e-mail e telefone de quem assinou dentro. Dá para mudar depois, a qualquer momento.
  5. Copie o segredo mostrado na hora. Ele não aparece de novo — é o que assina cada aviso que chega, e é assim que o seu sistema confirma que o POST veio do Firmou e não de qualquer um que descobriu a URL.

O aviso​

Um POST com JSON, para a URL cadastrada:

{
"event": "signed",
"occurred_at": "2026-08-20T14:31:12-03:00",
"document": {
"id": "019fd38e-d163-7347-a030-b34b261663dc",
"title": "Contrato de prestação de serviços",
"status": "pending_signatures",
"validation_url": "https://firmou.app/validar/019fd38e-d163-7347-a030-b34b261663dc"
},
"recipient": {
"id": "0e2f9a1c-...",
"name": "Fulano de Tal",
"email": "fulano@example.com",
"phone": null,
"status": "signed"
}
}

document.id é o uuid_public — o mesmo código impresso no documento e usado em GET /v1/documents/{id}, não o id interno. recipient vem null em eventos que são do documento como um todo (document_completed, document_sent, document_canceled, seal_failed) — os demais são sobre uma pessoa específica.

Não vai IP nem navegador de ninguém, nem o conteúdo do documento — o aviso sai para um endereço que o Firmou não controla, e vazar isso ali seria um vazamento com o nome do Firmou junto.

Eventos​

CódigoO que significa
document_completedTodos assinaram e o arquivo final está pronto
document_sentOs convites saíram para os signatários
document_canceledO emissor encerrou o documento antes do fim
seal_failedTodos assinaram, mas o selo não foi aplicado — o documento está parado
openedO signatário abriu o link pela primeira vez
consent_givenO signatário aceitou o meio de assinatura e pode assinar
signedUm signatário assinou. Chega uma vez por pessoa
declinedUm signatário recusou — o documento não se conclui
identity_verifiedO signatário conferiu o código de verificação
identity_verification_failedCódigo errado — útil para detectar tentativa de acesso indevido
link_expiredO prazo para assinar terminou sem a assinatura
signing_means_revokedO link daquele signatário foi invalidado por comprometimento
certificate_validation_failedO certificado ICP-Brasil apresentado não passou nas conferências

document_completed não existe na trilha interna do documento — para quem integra, "o documento ficou pronto" é um fato só, ainda que por dentro ele possa vir de mais de um caminho.

Verificando a assinatura​

Três cabeçalhos chegam em toda entrega:

CabeçalhoConteúdo
X-Firmou-EventO mesmo valor de event no corpo
X-Firmou-TimestampQuando o aviso foi montado (epoch, segundos)
X-Firmou-SignatureHMAC-SHA256 de {timestamp}.{corpo}, com o segredo do destino

O timestamp entra no cálculo — e não só ao lado dele — porque sem isso um aviso capturado hoje continuaria válido para sempre: reenviado amanhã, a assinatura ainda bateria sobre um fato antigo.

$esperado = hash_hmac(
'sha256',
$_SERVER['HTTP_X_FIRMOU_TIMESTAMP'].'.'.file_get_contents('php://input'),
$segredoDoWebhook,
);

if (! hash_equals($esperado, $_SERVER['HTTP_X_FIRMOU_SIGNATURE'])) {
http_response_code(401);
exit;
}

Use hash_equals() (ou equivalente na sua linguagem) — uma comparação === normal vaza timing e é o tipo de detalhe que vira falha de segurança séria bem depois de todo mundo ter esquecido dele.

Recuse também o aviso cujo X-Firmou-Timestamp tenha mais de cinco minutos. A assinatura sozinha não distingue o aviso que acabou de sair do aviso legítimo que alguém guardou e reenviou depois; a janela curta é o que fecha essa porta.

Sem essa conferência o seu endpoint aceita um POST de qualquer um que descubra a URL — e o que ele diz costuma disparar pagamento ou liberação de acesso do seu lado.

Reentrega e desligamento automático​

Uma entrega que falha (erro de rede, timeout, qualquer status HTTP fora de 2xx) é tentada de novo, até 6 vezes, com espera crescente: 10s, 1min, 5min, 30min, 2h. Cobre o reinício rápido, a manutenção de alguns minutos e a madrugada de quem só olha o alerta de manhã.

Depois de 20 falhas seguidas, o destino é desligado sozinho — um endpoint morto não pode consumir fila para sempre. A tela de Webhooks mostra o motivo do desligamento; religar zera a contagem de falhas.

Próximo passo​

Com o destino cadastrado, veja a Referência da API para saber o que cada evento tem a ver com o que você acabou de enviar em POST /v1/documents.