CyrusDocs

Webhooks

Receba notificações em tempo real sobre transações e eventos da plataforma.

Configuração

Webhooks são gerenciados pelo painel Cyrus em Configurações → Webhooks — não via API pública. Lá você pode:

  • Cadastrar URLs de destino
  • Selecionar os eventos que deseja receber
  • Visualizar o histórico de entregas e status
  • Obter o secret para verificação de autenticidade

A API pública não expõe endpoints para gerenciar webhooks. Use o painel do merchant.


Eventos disponíveis

| Evento | Descrição | |---|---| | transaction.created | Nova transação criada (cobrança ou payout) | | transaction.completed | Transação confirmada pelo banco | | transaction.failed | Transação falhou no processamento | | chargeback.created | Chargeback aberto para uma transação | | chargeback.updated | Status do chargeback atualizado | | withdrawal.created | Saque merchant solicitado | | withdrawal.completed | Saque merchant confirmado pelo banco |


Formato do payload

Todos os eventos seguem a mesma estrutura:

{
  "event": "transaction.completed",
  "data": {},
  "timestamp": "2024-01-15T12:04:22.000Z"
}

Payload por evento

transaction.completed — cobrança PIX recebida:

{
  "event": "transaction.completed",
  "data": {
    "transactionId": "cm1abc123def456",
    "txid": "E1234567820240115120000000000001",
    "endToEndId": "E1234567820240115120000000000001",
    "amount": 99.90,
    "paidAt": "2024-01-15T12:04:22.000Z"
  },
  "timestamp": "2024-01-15T12:04:22.000Z"
}

withdrawal.completed — payout confirmado:

{
  "event": "withdrawal.completed",
  "data": {
    "transactionId": "cm1xyz789abc012",
    "endToEndId": "E9876543220240115150000000000001",
    "amount": 150.00,
    "completedAt": "2024-01-15T15:00:08.000Z"
  },
  "timestamp": "2024-01-15T15:00:08.000Z"
}

chargeback.updated — estorno processado:

{
  "event": "chargeback.updated",
  "data": {
    "transactionId": "cm1abc123def456",
    "refundId": "REF123456",
    "amount": 99.90
  },
  "timestamp": "2024-01-15T16:00:00.000Z"
}

Headers da requisição

Cada evento entregue inclui dois headers:

| Header | Descrição | |---|---| | X-Cyrus-Event | Nome do evento (ex: transaction.completed) | | X-Cyrus-Signature | Secret do webhook cadastrado no painel |


Verificar autenticidade

Compare o valor do header X-Cyrus-Signature com o secret salvo para o webhook:

Node.js / Express
app.post('/webhooks/cyrus', express.json(), async (req, res) => {
  const signature = req.headers['x-cyrus-signature']
  const secret = process.env.CYRUS_WEBHOOK_SECRET
 
  if (signature !== secret) {
    return res.status(401).json({ error: 'Assinatura inválida' })
  }
 
  const { event, data } = req.body
 
  switch (event) {
    case 'transaction.completed':
      await confirmOrder(data.transactionId)
      break
    case 'withdrawal.completed':
      await markWithdrawalDone(data.transactionId)
      break
    case 'chargeback.updated':
      await handleChargeback(data.transactionId)
      break
  }
 
  res.status(200).json({ received: true })
})
Python / Flask
from flask import Flask, request, abort
import os
 
app = Flask(__name__)
 
@app.route('/webhooks/cyrus', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Cyrus-Signature', '')
    secret = os.environ['CYRUS_WEBHOOK_SECRET']
 
    if signature != secret:
        abort(401)
 
    payload = request.json
 
    if payload['event'] == 'transaction.completed':
        confirm_order(payload['data']['transactionId'])
 
    return {'received': True}, 200

Resposta esperada

Seu endpoint deve responder com HTTP 200 em até 10 segundos. Qualquer outra resposta é registrada como falha no log de webhooks do painel.


Idempotência

Use o campo timestamp combinado com data.transactionId para deduplicar eventos entregues mais de uma vez:

const key = event.event + ':' + event.data.transactionId
 
if (await redis.get(key)) {
  return res.status(200).json({ received: true })
}
 
await processEvent(event)
await redis.set(key, '1', 'EX', 86400)