Throne

Conta

Webhooks

Eventos enviados ao seu servidor, assinatura HMAC, retentativas, quando chegam e conversão de campanha pelo servidor.

Ver em Markdown

Cadastrar o endereço#

No painel, em Webhooks, adicione a URL do seu servidor. Você pode ter até 20 endereços, cada um com:

  • a própria URL, sempre em HTTPS;
  • a lista de eventos que quer receber (vazia = todos);
  • o próprio secret de assinatura, que você copia no painel e guarda no servidor.

O botão de teste envia um evento test.ping na hora, assinado como os outros:

JSON
{ "event_id": "...", "type": "test.ping", "version": "2026-05-01", "created_at": "2026-10-03T14:00:00Z",
  "data": { "message": "Webhook de teste do Throne, seu endpoint está funcionando!", "sent_at": "2026-10-03T14:00:00Z" } }

O histórico de entregas fica em Entregas de webhook.

Eventos#

Evento Quando
transaction.paid Pagamento confirmado: Pix, cartão (Sterling, Imperium, NovaCorp, Eminence, Apex, ShadowBank, Vortex) e Internacional (Paycore)
transaction.in_review Só na Vortex. O banco autorizou e a adquirente está analisando. O cartão já foi debitado, e a venda continua pending
withdrawal.completed Saque pago
withdrawal.failed Saque não pago (o valor volta para o saldo)
test.ping Botão de teste do painel

Só esses eventos existem. Recusa de cartão, expiração, estorno e contestação não geram webhook: acompanhe pelo GET /transactions/{id}.

O transaction.in_review vem desmarcado: marque-o no endpoint em Webhooks para começar a receber. Marcar ou desmarcar não muda nada nos outros eventos.

Recorrência não muda nada aqui: cada cobrança de ciclo chega como um transaction.paid normal, com o mesmo formato e com transaction_id próprio, como qualquer venda de cartão. Para ligar a cobrança à assinatura, consulte a recorrência, como em Recorrência.

transaction.in_review#

Só na Vortex. Nessa adquirente o banco autoriza na hora e a análise de risco da adquirente libera depois. Nesse intervalo o cartão do comprador já foi debitado e a venda continua pending para você.

O corpo tem os mesmos campos do transaction.paid, com os valores que uma venda pendente tem: amount_cents, fee_cents e net_amount_cents só nascem na liquidação, então chegam em zero, e status vem pending. Não existe status novo: quem diz "em análise" é o nome do evento.

JSON
{
  "type": "transaction.in_review",
  "data": {
    "transaction_id": "9f2c1d7e-4a8b-4c3d-8e9f-1a2b3c4d5e6f",
    "status": "pending",
    "payment_method": "credit_card",
    "amount_cents": 0,
    "fee_cents": 0,
    "net_amount_cents": 0,
    "card_brand": "visa",
    "card_last4": "4242",
    "currency": "BRL",
    "native_amount_cents": 49900,
    "customer": { "name": "Ana Lima", "email": "ana@exemplo.com" },
    "metadata": { "order_id": "ORD-2026-00001" },
    "created_at": "2026-10-29T18:04:11Z"
  }
}

Não libere acesso neste evento. Ele diz que o cartão foi debitado, não que a venda foi aprovada: a análise pode recusar, e aí a venda vira failed e o valor volta ao comprador. Quem libera é o transaction.paid.

Cada venda avisa uma vez. Reenvio da adquirente e releitura do seu lado não repetem o aviso.

Depois dele, três caminhos: a análise aprova e chega o transaction.paid; a análise recusa e a venda vira failed (sem webhook, como as outras recusas); ou a análise demora, e a venda segue pending até resolver.

Quando o webhook chega#

O transaction.paid sai quando o pagamento é confirmado. O tempo até ele chegar depende do meio de pagamento:

Meio Quando chega
Pix Logo depois da confirmação do pagamento
Cartão na Sterling e na Imperium Logo depois da aprovação, na maior parte das vendas. Uma venda em análise antifraude só gera o webhook quando a análise aprova
Cartão na NovaCorp Logo depois da aprovação quando o comprador volta da tela do banco. Se ele sair antes, a confirmação vem pela conferência automática, alguns minutos depois
Cartão na Eminence Logo depois da aprovação. Quando o banco pede verificação, depois que o comprador a conclui. Uma venda em análise antifraude só gera o webhook quando a análise aprova
Cartão na Apex Logo depois da aprovação. Uma venda em análise antifraude só gera o webhook quando a análise aprova
Cartão na ShadowBank Logo depois da aprovação. Uma venda em análise antifraude só gera o webhook quando a análise aprova
Cartão na Vortex Logo depois da aprovação. Uma venda em análise antifraude só gera o webhook quando a análise aprova
Internacional (Paycore) Logo depois da aprovação

Não cancele um pedido por falta de webhook antes da validade da cobrança (24 horas no cartão). Na dúvida, consulte GET /transactions/{id}.

Conversão de campanha pelo servidor#

Se você mede vendas de anúncio (pixel de compra), envie a conversão pelo seu servidor, ao receber transaction.paid, pela API de conversões da plataforma de anúncios. Não dependa só do evento throne-elements:paid da página:

  • na NovaCorp, quando o comprador vem de um navegador de app (Instagram, Facebook, Google, TikTok), a aba sai da sua página para pagar e nenhum evento de tela chega;
  • o comprador pode fechar a página antes do evento;
  • bloqueadores de anúncio derrubam pixels no navegador.

Para não contar a mesma compra duas vezes quando o pixel da página também disparar, use o transaction_id como identificador do evento nos dois lados. Assim a plataforma de anúncios junta os dois envios num só.

Formato#

Todo evento chega como POST com corpo JSON:

JSON
{
  "event_id": "8c1f3c5e-2c1a-4d1e-9a2b-1f0c9d7e6b5a",
  "type": "transaction.paid",
  "version": "2026-05-01",
  "created_at": "2026-10-03T14:21:07Z",
  "data": { }
}

transaction.paid#

Pix:

JSON
"data": {
  "transaction_id": "72394240-db89-4ec5-b370-4a3e7b998409",
  "status": "paid",
  "payment_method": "pix",
  "amount_cents": 4990,
  "fee_cents": 250,
  "net_amount_cents": 4740,
  "paid_at": "2026-10-03T14:21:05Z",
  "customer": { "name": "Maria Souza", "document": "12345678909", "email": "maria@exemplo.com" },
  "shipping_address": null,
  "items": null,
  "metadata": { "loja_pedido_id": "PED-1042" }
}

Cartão e Internacional trazem também card_brand, card_last4, currency, native_amount_cents e fx_rate:

JSON
"data": {
  "transaction_id": "ae3bf1da-0c9b-4818-9140-c6432152c23e",
  "status": "paid",
  "payment_method": "credit_card",
  "amount_cents": 31237,
  "fee_cents": 4148,
  "net_amount_cents": 27089,
  "paid_at": "2026-10-03T14:30:58Z",
  "card_brand": "visa",
  "card_last4": "4242",
  "currency": "CHF",
  "native_amount_cents": 4990,
  "fx_rate": "6.26000000",
  "customer": { "name": "Luca Meier", "email": "luca@example.ch" },
  "shipping_address": null,
  "items": null,
  "metadata": {}
}

fx_rate chega como texto com 8 casas. Trate os campos que não usa (ou que vierem null) como opcionais: qualquer transaction.paid pode trazer o conjunto completo.

withdrawal.completed e withdrawal.failed#

JSON
"data": { "withdrawal_id": "0b8f3a52-6c1d-4e2f-9a7b-3c4d5e6f7a8b", "amount_cents": 50000, "completed_at": "2026-10-03T14:45:12Z" }
JSON
"data": { "withdrawal_id": "0b8f3a52-6c1d-4e2f-9a7b-3c4d5e6f7a8b", "amount_cents": 50000, "reason": "Chave Pix inexistente" }

O reason de withdrawal.failed pode vir técnico. Para mostrar ao seu cliente, use o failure_reason do GET /withdrawals/{id}, que já vem em linguagem amigável.

Validar a assinatura#

Cada entrega traz o header X-Throne-Signature: o HMAC-SHA256, em hexadecimal, do corpo cru da requisição, com o secret do endereço. Calcule sobre os bytes exatamente como chegaram, antes de interpretar o JSON (reformatar o JSON muda a assinatura).

$body = file_get_contents('php://input');
$recebida = $_SERVER['HTTP_X_THRONE_SIGNATURE'] ?? '';
$esperada = hash_hmac('sha256', $body, getenv('THRONE_WEBHOOK_SECRET'));

if (! hash_equals($esperada, $recebida)) {
    http_response_code(401);
    exit;
}

$evento = json_decode($body, true);
http_response_code(200); // responda rápido; processe em segundo plano

Entrega e retentativas#

  • Responda 2xx em até 3 segundos. Grave o evento e processe depois; processamento pesado dentro da requisição faz a entrega falhar por tempo.
  • Sem 2xx, o Throne tenta de novo: até 10 tentativas no total (a primeira e mais 9), com intervalos crescentes, de segundos até algumas horas.
  • Responda 2xx também aos tipos que você não trata (como test.ping), para não gerar retentativas.
  • Use a URL final. A entrega não segue redirecionamento como POST: uma URL que redireciona (por exemplo, de http para https, ou com e sem www) faz a entrega falhar.
  • Endereços internos (como localhost ou 192.168.x.x) não recebem entregas.

Não processe duas vezes#

O mesmo pagamento pode chegar mais de uma vez (retentativas, ou mais de um endereço cadastrado). O event_id é diferente para cada endereço, então deduplique pelo conteúdo:

  • transaction.paid: pela dupla type + data.transaction_id;
  • saques: por type + data.withdrawal_id.

Se o evento já foi processado, responda 200 de novo sem refazer nada.

Quando não chega webhook#

Recusa e expiração de cartão, expiração de Pix, estornos e contestações não geram evento, e alguns saques terminam sem webhook. Para esses casos, consulte:

  • GET /transactions/{id} para cobranças e checkouts;
  • GET /withdrawals/{id} para saques.