Webhooks
Webhooks notify your system the moment something happens (a signer signed, the envelope was completed), without you having to query the API repeatedly. You register an HTTPS address and Ravisign sends a JSON POST for each event, signed with HMAC-SHA256.
Webhooks can be registered through the API (actions below) or in the panel, in Settings > Webhooks.
In the examples, the key is in the RAVISIGN_CHAVE environment variable and the responses were shortened where indicated:
export RAVISIGN_CHAVE="rsg_..."
API="https://ravisign.com.br/api/v1"webhooks/criar#
POST, scope envelopes.escrever. Parameters url (https, public address, up to 500 characters) and eventos
(list or comma-separated text; empty = all events). Up to 10 webhooks per account (409 webhook_limite).
curl -sS "$API/webhooks/criar" -H "Authorization: Bearer $RAVISIGN_CHAVE" \
-H "Content-Type: application/json" \
-d '{"url":"https://integrador.exemplo.com.br/ravisign","eventos":["envelope.concluido","signatario.otp_solicitado"]}'{
"ok": true,
"segredo": "rsgw_48e39ee70a218e351b7020ec4016abc4f917c72ca44356c8",
"webhook": {
"id": 1,
"url": "https://integrador.exemplo.com.br/ravisign",
"eventos": ["envelope.concluido", "signatario.otp_solicitado"],
"ativo": true,
"criado_em": "2026-10-09T20:28:25-03:00",
"ultima_entrega": null
}
}The segredo appears only in this response: keep it to verify the signature of deliveries (see Webhooks). Errors: 400
webhook_url_invalida (no https, internal or invalid address), 400 webhook_eventos_invalidos.
webhooks/listar#
GET, scope envelopes.ler. The account's webhooks, without the secret, with the last delivery.
curl -sS "$API/webhooks/listar" -H "Authorization: Bearer $RAVISIGN_CHAVE"{
"ok": true,
"webhooks": [
{
"id": 1,
"url": "https://integrador.exemplo.com.br/ravisign",
"eventos": ["envelope.concluido", "signatario.otp_solicitado"],
"ativo": true,
"criado_em": "2026-10-09T20:28:25-03:00",
"ultima_entrega": {
"situacao": "entregue",
"http_status": 200,
"tentativas": 1,
"criada_em": "2026-10-09T20:31:02-03:00",
"entregue_em": "2026-10-09T20:31:03-03:00"
}
}
]
}ultima_entrega is null while there has been no delivery.
webhooks/testar#
POST, scope envelopes.escrever. Parameter id. Immediately sends a webhook.teste event to the address and returns the
result.
curl -sS "$API/webhooks/testar" -H "Authorization: Bearer $RAVISIGN_CHAVE" \
-H "Content-Type: application/json" -d '{"id":1}'{ "ok": true, "id": 1, "entregue": true, "situacao": "entregue", "http_status": 200 }If the destination does not respond 2xx, entregue is false, situacao is pendente (the delivery follows the new
attempts described in Webhooks) and http_status contains the status received (or null with no response). Statuses of a delivery:
pendente, entregue and falhou (attempts exhausted).
webhooks/remover#
POST, scope envelopes.escrever. Parameter id.
curl -sS "$API/webhooks/remover" -H "Authorization: Bearer $RAVISIGN_CHAVE" \
-H "Content-Type: application/json" -d '{"id":1}'{ "ok": true, "id": 1, "removido": true }Events and deliveries#
Ravisign notifies your system with a JSON POST for each event the webhook is subscribed to.
| Event | When | dados |
|---|---|---|
envelope.enviado |
the envelope was sent to the signers | {} |
signatario.visualizou |
the signer opened the document | signatario |
signatario.assinou |
the signer signed | signatario |
signatario.recusou |
the signer declined (the envelope becomes recusado) |
signatario |
envelope.concluido |
everyone signed and the final copy is ready | hash_final |
envelope.expirado |
the deadline passed without all signatures | {} |
envelope.cancelado |
the envelope was canceled | {} |
signatario.otp_solicitado |
on the conversation channel, the signer requested a new code on the page | signatario, codigo, validade_minutos, expira_em |
webhook.teste |
webhooks/testar (the body's envelope is null) |
mensagem |
signatario in the data is { "uuid", "papel", "ordem" }. The body never includes name, email address, phone number or CPF: use the
uuids to match with what your system already has.
Example body (envelope.concluido):
{
"evento": "envelope.concluido",
"ocorrido_em": "2026-10-09T23:31:02Z",
"envelope": {
"uuid": "157b650a-75e0-4eaa-a32c-b92b5a432ee4",
"referencia_externa": "PEDIDO-1042",
"situacao": "concluido",
"origem": "chatisp"
},
"dados": { "hash_final": "7ced225baf8e20d34cb09a713c4454a632f749ecf31645aa3a640e1660e2a880" }
}Example body (signatario.otp_solicitado):
{
"evento": "signatario.otp_solicitado",
"ocorrido_em": "2026-10-09T23:40:00Z",
"envelope": {
"uuid": "157b650a-75e0-4eaa-a32c-b92b5a432ee4",
"referencia_externa": "PEDIDO-1042",
"situacao": "enviado",
"origem": "chatisp"
},
"dados": {
"signatario": { "uuid": "7eb90fca-1488-4a17-84c4-0b01f4acb9f1" },
"codigo": "815204",
"validade_minutos": 30,
"expira_em": "2026-10-10T00:10:00Z"
}
}Headers of each delivery:
| Header | Content |
|---|---|
Content-Type |
application/json |
User-Agent |
Ravisign-Webhook/1 |
X-Ravisign-Evento |
the event (same as evento in the body) |
X-Ravisign-Entrega |
delivery identifier; the same in all attempts (use it to ignore repeats) |
X-Ravisign-Tentativa |
attempt number (1 to 8) |
X-Ravisign-Assinatura |
sha256= followed by the HMAC-SHA256 of the body, in hexadecimal, with the webhook secret |
Delivery and retries:
- Respond with any 2xx status within 10 seconds. Process the event after responding, if it takes long.
- Without 2xx (or without a response), Ravisign tries again after 1, 5, 15, 60 and 240 minutes and, from then on, every 240 minutes, up to 8 attempts in total. Redirects are not followed.
- The destination must be https with a public address.
- The order of arrival is not guaranteed: use
ocorrido_emand, if in doubt, confirm withenvelopes/detalhe.
Signature verification (PHP)#
Calculate the HMAC over the RAW body received (before decoding the JSON) and compare in constant time:
<?php
$segredo = getenv('RAVISIGN_WEBHOOK_SEGREDO'); // o "segredo" devolvido por webhooks/criar
$corpo = file_get_contents('php://input');
$recebida = $_SERVER['HTTP_X_RAVISIGN_ASSINATURA'] ?? '';
$esperada = 'sha256=' . hash_hmac('sha256', $corpo, $segredo);
if (!hash_equals($esperada, $recebida)) {
http_response_code(401);
exit;
}
$entrega = $_SERVER['HTTP_X_RAVISIGN_ENTREGA'] ?? '';
// se esta entrega já foi processada (mesmo X-Ravisign-Entrega), apenas responda 200
$evento = json_decode($corpo, true);
http_response_code(200); // responda logo; o trabalho pesado vai para depois
switch ($evento['evento']) {
case 'envelope.concluido':
// baixar a via final com documentos/baixar e guardar $evento['dados']['hash_final']
break;
case 'signatario.otp_solicitado':
// entregar $evento['dados']['codigo'] na conversa do signatário $evento['dados']['signatario']['uuid']
break;
}Contact Ravi Systems support at the e-mail below.