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:
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 })
})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}, 200Resposta 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:
if (await redis.get(key)) {
return res.status(200).json({ received: true })
}
await processEvent(event)
await redis.set(key, '1', 'EX', 86400)