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 completed como imutável. Um document.failed que chega depois de um completed para 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-Delivery ou 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.