SignXP Docs
Callbacks
Quando um documento muda de estado, o SignXP faz um POST para o callback_url informado no envio.
Corpo
{
"event": "document.completed",
"id": 128,
"reference": "acme",
"external_id": "contrato-2026-0001",
"environment": "live",
"status": "completed",
"completed_at": "2026-07-28T14:03:11-03:00",
"verification_url": "https://app.signxp.com.br/validar/9f2c...",
"signers": [
{ "key": "contratante", "email": "maria@empresa.com.br", "status": "signed", "signed_at": "2026-07-28T13:58:02-03:00" }
]
}
Eventos possíveis:
| Evento | Quando acontece |
|---|---|
document.completed |
Todos assinaram e o PDF selado está disponível. |
document.rejected |
Um signatário recusou — ou o licenciado recusou na conferência manual. |
document.failed |
Falha no processamento; veja error. |
document.cancelled |
A tentativa foi encerrada e os links pendentes deixaram de valer — por você, pela API, pelo licenciado no portal dele, ou pela equipe SignXP a pedido. Sai uma vez por documento: repetir o cancelamento não reemite o evento. |
document.placed |
Campos posicionados; os signatários foram liberados para assinar (e o convite saiu, para quem recebe por e-mail). |
document.awaiting_review |
Todos assinaram, mas a validação por IA não concluiu e o licenciado precisa conferir as imagens. A conclusão fica retida até a decisão dele. |
O document.awaiting_review não é um desfecho: depois dele ainda vem um
document.completed ou um document.rejected. Trate-o como aviso de espera.
O campo reference identifica a conta dona do documento. Se você integra uma
conta só, ele é sempre o mesmo e pode ser ignorado. Ele existe para quem recebe
notificações de várias contas no mesmo endpoint — o caso de quem embute o
SignXP num produto multiempresa.
Um completed pode ser desmentido
A cópia certificada é montada depois que o document.completed sai — selar
leva tempo e ninguém fica esperando por isso. Se a selagem não fechar, o
documento volta para failed e você recebe um document.failed com o mesmo
id. Quando uma retentativa consegue selar, vem um novo document.completed.
É raro, mas não é hipotético. Duas consequências para o seu código:
- Não trate
completedcomo imutável. Umdocument.failedque chega depois de umcompletedpara o mesmo documento é a correção, não um evento fora de ordem: o estado atual é sempre o do último evento. - Baixe a via final quando ela importar, não no instante do
completed. Se o download responder 404 logo depois do aviso, a selagem ainda está em curso — tente de novo em alguns minutos ou espere o desfecho se estabilizar.
Cabeçalhos
| Cabeçalho | Conteúdo |
|---|---|
X-SignXP-Event |
Nome do evento. |
X-SignXP-Delivery |
UUID da entrega. O mesmo valor se repete nas retentativas. |
X-SignXP-Reference |
Conta dona do documento — o mesmo valor do campo reference do corpo. Repetido aqui para você rotear a entrega sem precisar desserializar o JSON antes. |
X-SignXP-Timestamp |
Unix timestamp da assinatura. |
X-SignXP-Signature |
t=<timestamp>,v1=<hmac> |
Validando a assinatura
A assinatura é um HMAC-SHA256 de "<timestamp>.<corpo bruto>", usando o segredo de callback da sua licença como chave. Sempre valide antes de confiar no conteúdo — e sempre com o corpo bruto, antes de qualquer parse.
// PHP
$payload = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_SIGNXP_SIGNATURE'] ?? '';
$secret = getenv('SIGNXP_CALLBACK_SECRET');
parse_str(str_replace(',', '&', $header), $parts); // t=..., v1=...
$expected = hash_hmac('sha256', $parts['t'].'.'.$payload, $secret);
if (! hash_equals($expected, $parts['v1'] ?? '')) {
http_response_code(401);
exit;
}
// Rejeite entregas antigas (proteção contra replay).
if (abs(time() - (int) $parts['t']) > 300) {
http_response_code(401);
exit;
}
// Node.js (Express com body bruto)
const crypto = require('crypto');
app.post('/webhooks/signxp', express.raw({ type: 'application/json' }), (req, res) => {
const header = req.get('X-SignXP-Signature') || '';
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto
.createHmac('sha256', process.env.SIGNXP_CALLBACK_SECRET)
.update(`${parts.t}.${req.body}`)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ''))) {
return res.sendStatus(401);
}
// ... processa e responde rápido
res.sendStatus(200);
});
O que esperamos do seu endpoint
- Responder 2xx em até 15 segundos. Faça o processamento pesado em fila.
- Ser idempotente: a mesma entrega pode chegar mais de uma vez. Use
X-SignXP-Deliveryou o par (id,status) para deduplicar.
Falhas são registradas e reenviadas automaticamente (até 3 tentativas). Você acompanha todas as entregas em Registros, no portal da sua conta, e reenvia você mesmo a entrega que ficou pelo caminho — sem depender do suporte.
Trocando o segredo
O segredo de callback pode ser regenerado a pedido. O valor antigo para de validar imediatamente, então combine a troca com a sua equipe antes.