novexFINTECH Docs

Acompanhar

Webhooks

Receber os eventos em tempo real e validar a assinatura.

Webhook é como você fica sabendo de um pagamento no instante em que ele acontece. Você cadastra uma URL, nós enviamos um POST assinado a cada evento — sem você precisar perguntar nada.

Cadastrar o endpoint

POST/v1/webhooks
Requisição
curl -X POST https://novexfinance.com.br/api/v1/webhooks \
  -H "Authorization: Bearer $NOVEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://sualoja.com.br/webhooks/novex",
    "events": "all"
  }'
Resposta 201
{
  "object": "webhook_endpoint",
  "id": "whe_12",
  "url": "https://sualoja.com.br/webhooks/novex",
  "events": "all",
  "active": true,
  "secret": "whsec_6a1f92c04b7e35d8fa1c60b394e27d5081af6c3b92e4d70a",
  "created_at": "2026-08-07T14:32:10-03:00"
}

Guarde o secret agora. Ele aparece uma única vez, é o que valida a assinatura de cada evento, e não temos como reexibi-lo depois. Se perder, remova o endpoint e cadastre outro.

CampoTipoDescrição
urlobrigatóriostringURL que vai receber os eventos. Precisa ser HTTPS e responder em até 8 segundos.
eventsopcionalstring"all" (padrão), "transaction" (só cobranças) ou "withdrawal" (só saques).

Você pode cadastrar até 10 endpoints. URLs repetidas são recusadas com 409.

Eventos

EventoQuando disparaAção típica
charge.paidPagamento confirmado.Liberar o pedido.
charge.refusedCartão não autorizado pelo emissor.Avisar o cliente e oferecer outro meio.
charge.failedA cobrança não pôde ser concluída.Cancelar o pedido.
charge.refundedValor devolvido ao pagador.Reverter a liberação.
charge.chargebackContestação aceita; valor debitado do saldo.Reverter a liberação e registrar a perda.
charge.disputedO portador abriu contestação.Separar comprovantes de entrega.
charge.authorizedCartão autorizado, ainda não capturado.Informativo.
charge.processingPagamento em processamento.Informativo.
withdrawal.paidSaque concluído na conta de destino.Baixar no seu financeiro.
withdrawal.failedSaque falhou; valor volta ao saldo.Verificar a chave PIX.
withdrawal.rejectedSaque recusado.Verificar a chave PIX.

Não enviamos evento na criação da cobrança — você já recebeu esses dados na resposta do POST. O primeiro webhook chega quando o status muda.

Formato do evento

Corpo do POST
{
  "id": "evt_ac41f9b26d80375e1c4a",
  "object": "event",
  "type": "charge.paid",
  "created_at": "2026-08-07T14:35:02-03:00",
  "data": {
    "object": "charge",
    "id": "ch_9f2a71c4e8b35d06a147",
    "status": "paid",
    "amount": 12990,
    "currency": "BRL",
    "payment_method": "pix",
    "description": "Pedido #48219",
    "reference": "48219",
    "customer": {
      "name": "Maria Oliveira",
      "email": "maria@exemplo.com.br",
      "document": "39053344705"
    },
    "fee_amount": 129,
    "net_amount": 12861,
    "available_at": "2026-08-07T14:35:02-03:00",
    "paid_at": "2026-08-07T14:35:02-03:00",
    "metadata": {
      "canal": "app-ios"
    }
  }
}

O objeto dentro de data é exatamente o mesmo que você recebe consultando a cobrança pela API.

Headers enviados

HeaderConteúdo
Novex-Signaturet=<unix>,v1=<hmac-sha256> — a assinatura.
Novex-Event-IdID único do evento. Use para não processar duas vezes.
Novex-Event-TypeTipo do evento, ex.: charge.paid.
Novex-Delivery-AttemptNúmero da tentativa, a partir de 1.

Validar a assinatura

Valide sempre, antes de olhar o conteúdo. A sua URL de webhook é pública: qualquer um pode enviar um POST dizendo que um pedido foi pago. Sem validação, você entrega produto de graça para quem descobrir o endereço.

A assinatura é um HMAC-SHA256 sobre <timestamp>.<corpo cru>, com o secret do endpoint como chave. Três passos:

  1. Leia o corpo cru da requisição — a string exata, antes de qualquer parse de JSON.
  2. Extraia t e v1 do header Novex-Signature.
  3. Calcule o HMAC de t + "." + corpo e compare com v1 em tempo constante.
Validação completa
<?php

$secret = getenv('NOVEX_WEBHOOK_SECRET');
$payload = file_get_contents('php://input'); // corpo CRU
$header = $_SERVER['HTTP_NOVEX_SIGNATURE'] ?? '';

// t=1786... ,v1=abc...
parse_str(str_replace(',', '&', $header), $parts);
$timestamp = (int) ($parts['t'] ?? 0);
$signature = (string) ($parts['v1'] ?? '');

// 1. janela de 5 minutos: barra reenvio de um evento capturado
if (abs(time() - $timestamp) > 300) {
    http_response_code(400);
    exit;
}

// 2. hash_equals: comparação em tempo constante
$expected = hash_hmac('sha256', $timestamp . '.' . $payload, $secret);
if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit;
}

// 3. assinatura válida — agora sim dá para confiar no conteúdo
$event = json_decode($payload, true);

if ($event['type'] === 'charge.paid') {
    $pedido = $event['data']['reference'];
    liberarPedido($pedido, $event['id']);
}

http_response_code(200);
echo 'ok';

Três detalhes que costumam quebrar a validação:

  • Usar o JSON já parseado e re-serializado em vez do corpo cru. Espaçamento e ordem de chaves mudam, e o hash não bate.
  • Comparar com ==. Use hash_equals, timingSafeEqual ou compare_digest — comparação comum vaza informação pelo tempo de execução.
  • Esquecer de conferir o t. Sem a janela de tempo, uma cópia capturada do evento pode ser reenviada meses depois com assinatura ainda válida.

Como responder

Responda 200 assim que gravar o evento. Qualquer status fora da faixa 2xx conta como falha e entra na fila de reenvio.

Processe depois de responder. Se você emite nota fiscal, envia e-mail e atualiza o estoque antes de responder, o webhook estoura os 8 segundos de timeout, e nós reenviamos — mesmo que tudo tenha dado certo do seu lado. Grave o evento, responda 200, processe em fila.

Idempotência do seu lado

Um evento pode chegar mais de uma vez: falha de rede, reenvio, timeout. Guarde o event.id e ignore o que já processou:

Evitar processar duas vezes
// coluna UNIQUE em event_id resolve a corrida entre dois
// webhooks simultâneos melhor do que um SELECT antes do INSERT
try {
    $db->prepare('INSERT INTO eventos_novex (event_id, tipo) VALUES (?, ?)')
       ->execute([$event['id'], $event['type']]);
} catch (PDOException $e) {
    if ((int) $e->errorInfo[1] === 1062) {
        http_response_code(200); // já processado
        exit;
    }
    throw $e;
}

processar($event);

Reenvio automático

Se a entrega falhar, tentamos de novo com intervalos crescentes — até 6 tentativas ao longo de aproximadamente 30 horas:

TentativaQuando
1ªImediatamente
2ª~1 minuto depois
3ª~5 minutos depois
4ª~30 minutos depois
5ª~2 horas depois
6ª~6 horas depois

Após 20 falhas seguidas, o endpoint é desativado — continuar tentando numa URL morta só atrasaria os eventos dos endpoints que funcionam. Para reativar, cadastre-o novamente.

Depurar entregas

GET/v1/webhook-deliveries

Veja o que enviamos, quando, e o que o seu servidor respondeu:

Resposta 200
{
  "object": "list",
  "data": [
    {
      "object": "webhook_delivery",
      "event_id": "evt_ac41f9b26d80375e1c4a",
      "event": "charge.paid",
      "object_type": "charge",
      "object_id": "ch_9f2a71c4e8b35d06a147",
      "url": "https://sualoja.com.br/webhooks/novex",
      "attempt": 1,
      "response_code": 500,
      "delivered": false,
      "next_retry_at": "2026-08-07T14:36:02-03:00",
      "created_at": "2026-08-07T14:35:02-03:00"
    }
  ],
  "has_more": false
}

Listar e remover endpoints

GET/v1/webhooks
DELETE/v1/webhooks/{id}
Requisições
# listar (o secret não é devolvido aqui)
curl https://novexfinance.com.br/api/v1/webhooks \
  -H "Authorization: Bearer $NOVEX_API_KEY"

# remover
curl -X DELETE https://novexfinance.com.br/api/v1/webhooks/whe_12 \
  -H "Authorization: Bearer $NOVEX_API_KEY"

A listagem mostra consecutive_failures e last_status_code — bons indicadores de que o seu endpoint parou de responder.