Webhooks
Configure webhooks para receber notificações em tempo real quando eventos importantes acontecerem, como confirmação de pagamentos.
O que são Webhooks?
Webhooks são chamadas HTTP que o ActivePayments envia para seu servidor quando eventos importantes acontecem. Ao invés de você consultar a API repetidamente (polling), você recebe uma notificação instantânea.
Tempo Real
Receba notificações instantaneamente quando pagamentos forem confirmados
Seguro
Assinatura HMAC para garantir autenticidade das mensagens
Retry Automático
Tentativas automáticas em caso de falha na entrega
Eventos Disponíveis
| Evento | Descrição |
|---|---|
charge.paid | Pagamento de cobrança PIX ou Boleto confirmado |
charge.cancelled | Cobrança PIX cancelada manualmente |
charge.expired | Cobrança PIX expirou sem pagamento |
charge.failed | Pagamento falhou por erro técnico |
billet.paid | Boleto foi pago e compensado (1-3 dias úteis) |
billet.expired | Boleto venceu sem pagamento |
card.approved | Pagamento com cartão de crédito aprovado — o payload inclui o bloco card (brand, last4, installments) |
card.refused | Pagamento com cartão recusado pelo emissor/antifraude |
card.refunded | Cobrança de cartão estornada (total) — o campo card.refundedAmount traz o acumulado |
card.chargeback | Cobrança de cartão contestada pelo portador (chargeback) — também gera charge.dispute_opened |
withdrawal.approved | Solicitação de saque foi aprovada e está sendo processada |
withdrawal.completed | Saque foi processado e transferido com sucesso |
withdrawal.failed | Saque falhou por erro técnico (será reembolsado automaticamente) |
withdrawal.rejected | Saque foi rejeitado (chave PIX inválida, dados incorretos, etc.) |
recurrence.approved | Assinatura Pix Automático autorizada pelo pagador. Veja Pix Automático. |
recurrence.rejected | Autorização da assinatura recusada pelo pagador ou pelo banco dele |
recurrence.expired | QR Code de adesão da assinatura expirou sem autorização |
recurrence.cancelled | Assinatura cancelada (pelo integrador, pelo pagador ou pelo PSP) |
recurrence.charge.created | Cobrança de um ciclo da assinatura foi criada |
recurrence.charge.paid | Ciclo da assinatura liquidado (valor líquido creditado no saldo) |
recurrence.charge.failed | Ciclo da assinatura falhou (após esgotar as retentativas, se houver) |
subaccount.approved | Subconta aprovada (KYC verificado) — já pode receber split. Veja Split de Pagamentos. |
subaccount.rejected | Subconta rejeitada na análise — o campo rejectionReason traz o motivo |
subaccount.transfer.completed | Repasse do saldo da subconta transferido via PIX (automático ou sob demanda) |
subaccount.transfer.failed | Repasse da subconta falhou — o valor volta para o saldo da subconta |
usdt.conversion.completed | Conversão BRL → USDT concluída — USDT enviado à wallet informada |
usdt.conversion.failed | Conversão BRL → USDT falhou — o valor debitado volta ao saldo |
charge.dispute_opened | Uma MED (Mecanismo Especial de Devolução Pix) ou chargeback foi aberto. Veja MED. |
charge.dispute_resolved | Disputa resolvida. Campo result: WON (saldo mantido), LOST (saldo debitado) ou EXPIRED. |
ping | Evento de teste para verificar o endpoint |
Estrutura do Payload
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
event | string | Sim | Tipo do evento (ex: charge.paid) |
timestamp | string | Sim | Data/hora do evento em formato ISO 8601 |
data | object | Sim | Dados do evento (varia conforme o tipo) |
Exemplo: charge.paid
{
"event": "charge.paid",
"timestamp": "2024-01-15T10:05:23.000Z",
"data": {
"chargeId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"externalId": "txid_abc123xyz",
"amount": 150.50,
"netAmount": 145.99,
"fee": 4.51,
"status": "paid",
"method": "PIX",
"endToEnd": "E12345678202401151005abc123xyz456",
"customer": {
"name": "João da Silva",
"cpf": "12345678901",
"email": "joao@email.com"
},
"paidAt": "2024-01-15T10:05:23.000Z",
"externalReference": "ORDER-12345",
"split": [
{
"subAccountId": "b2c3d4e5-...",
"subAccountName": "Maria Recebedora",
"type": "PERCENT",
"value": 30,
"amount": 43.80,
"status": "CREDITED"
}
]
}
}Campos do objeto data (charge.paid)
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
chargeId | string | Sim | ID interno da cobrança |
externalId | string | Sim | ID externo da transação no gateway |
amount | number | Sim | Valor bruto da cobrança em R$ |
netAmount | number | Sim | Valor líquido após taxas |
fee | number | Sim | Total de taxas cobradas |
status | string | Sim | Status atual (paid, cancelled, etc.) |
method | string | Sim | Método de pagamento (PIX, BILLET, CREDIT_CARD) |
endToEnd | string | Não | ID End-to-End do PIX (E2E) - identificador único da transação no Banco Central. Disponível apenas para pagamentos PIX confirmados. |
customer | object | Sim | Dados do cliente (name, cpf, email) |
paidAt | string | Não | Data/hora do pagamento em ISO 8601 |
externalReference | string | Não | Referência externa enviada na criação |
split | array | Não | Presente APENAS quando a cobrança foi criada com split de pagamentos. Cada item: subAccountId, subAccountName, type (PERCENT/FIXED), value, amount (R$ calculado) e status (PENDING, CREDITED, CANCELLED) |
Exemplo: withdrawal.completed
{
"event": "withdrawal.completed",
"timestamp": "2024-01-15T14:30:00.000Z",
"data": {
"withdrawalId": "wd_e5f6-7890-abcd-ef1234567890",
"amount": 500.00,
"netAmount": 497.00,
"fee": 3.00,
"status": "completed",
"pixKey": "joao@email.com",
"pixKeyType": "EMAIL",
"endToEnd": "E12345678202401151430xyz789abc123",
"processedAt": "2024-01-15T14:30:00.000Z"
}
}Eventos de assinatura (Pix Automático)
Assinaturas criadas via Pix Automático geram os eventos recurrence.*, enviados aos mesmos endpoints cadastrados e assinados com o mesmo mecanismo HMAC dos demais eventos.
Eventos da assinatura
recurrence.approved, recurrence.rejected, recurrence.expired e recurrence.cancelled notificam mudanças de status da assinatura:
{
"event": "recurrence.approved",
"recurrenceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"idRec": "RR1234567820260810abcdefghijk",
"status": "APPROVED",
"amount": "49.90",
"periodicity": "MONTHLY",
"externalReference": "ASSINATURA-001",
"customer": {
"name": "João da Silva",
"cpf": "12345678901"
}
}| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
event | string | Sim | Tipo do evento (recurrence.approved, recurrence.rejected, recurrence.expired, recurrence.cancelled) |
recurrenceId | string | Sim | ID da assinatura no ActivePayments |
idRec | string | Sim | Identificador da recorrência no arranjo Pix (BCB) |
status | string | Sim | Status atual da assinatura (APPROVED, REJECTED, EXPIRED, CANCELLED) |
amount | string | Sim | Valor de cada ciclo em R$ (string decimal) |
periodicity | string | Sim | WEEKLY, MONTHLY, QUARTERLY, SEMIANNUAL ou ANNUAL |
externalReference | string | Não | Referência externa enviada na criação da assinatura |
customer | object | Sim | Dados do pagador (name, cpf) |
reason | string | Não | Motivo, quando disponível (ex: rejeição pelo banco ou cancelamento com justificativa) |
Eventos dos ciclos de cobrança
recurrence.charge.created, recurrence.charge.paid e recurrence.charge.failed acompanham cada ciclo da assinatura e trazem, além dos campos acima, o objeto charge:
{
"event": "recurrence.charge.paid",
"recurrenceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"idRec": "RR1234567820260810abcdefghijk",
"status": "APPROVED",
"amount": "49.90",
"periodicity": "MONTHLY",
"externalReference": "ASSINATURA-001",
"customer": {
"name": "João da Silva",
"cpf": "12345678901"
},
"charge": {
"chargeId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"transactionId": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"externalId": "txid_abc123xyz",
"cycle": 2,
"dueDate": "2026-09-10",
"amount": "49.90",
"endToEndId": "E12345678202609101005abc123xyz456",
"paidAt": "2026-09-10T10:05:23.000Z"
}
}| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
charge.chargeId | string | Sim | ID da cobrança do ciclo |
charge.transactionId | string | Não | ID da transação no ActivePayments (presente quando o ciclo vira transação) |
charge.externalId | string | Sim | ID da cobrança no gateway (txid) |
charge.cycle | number | Sim | Número sequencial do ciclo (1, 2, 3...) |
charge.dueDate | string | Sim | Data de vencimento do ciclo (YYYY-MM-DD) |
charge.amount | string | Sim | Valor do ciclo em R$ (string decimal) |
charge.endToEndId | string | Não | ID End-to-End do PIX — presente apenas em ciclos pagos |
charge.paidAt | string | Não | Data/hora do pagamento em ISO 8601 — presente apenas em ciclos pagos |
charge.reason | string | Não | Motivo da falha — presente apenas em recurrence.charge.failed |
charge.paid continua sendo disparado para a transação de cada ciclo e para a primeira cobrança da assinatura — integrações que só ouvem charge.paid funcionam sem nenhuma mudança.Eventos de Subcontas (Split)
Quando uma subconta criada por você (painel ou API) passa pela análise da nossa equipe, os eventos subaccount.approved e subaccount.rejected são enviados aos endpoints cadastrados:
{
"event": "subaccount.approved",
"timestamp": "2026-07-09T14:00:00.000Z",
"data": {
"subAccountId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Maria Recebedora",
"document": "12345678901",
"pixKey": "maria@email.com",
"status": "APPROVED"
}
}| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
event | string | Sim | subaccount.approved ou subaccount.rejected |
subAccountId | string | Sim | ID da subconta — use no campo split das cobranças |
name | string | Sim | Nome do titular da subconta |
document | string | Sim | CPF/CNPJ do titular |
pixKey | string | Sim | Chave PIX de recebimento dos repasses |
status | string | Sim | APPROVED ou REJECTED |
rejectionReason | string | Não | Motivo — presente apenas em subaccount.rejected |
Repasses da subconta
Cada repasse do saldo da subconta (automático diário ou sob demanda via API/painel) gera subaccount.transfer.completed quando o PIX é liquidado, ou subaccount.transfer.failed quando falha (o valor é estornado à subconta — e a taxa, quando paga pela conta principal, volta para ela):
{
"event": "subaccount.transfer.completed",
"timestamp": "2026-07-18T11:05:00.000Z",
"data": {
"transferId": "f0e1d2c3-b4a5-6789-0abc-def123456789",
"subAccountId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"subAccountName": "Maria Recebedora",
"externalReference": "repasse-loja-01",
"amount": 150.5,
"netAmount": 148.5,
"fee": 2,
"feeChargedTo": "SUBACCOUNT",
"status": "completed",
"failedReason": null,
"pixKey": "maria@email.com",
"pixKeyType": "EMAIL",
"endToEnd": "E12345678202607181105abcdef12345",
"createdAt": "2026-07-18T11:00:00.000Z",
"processedAt": "2026-07-18T11:05:00.000Z"
}
}| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
event | string | Sim | subaccount.transfer.completed ou subaccount.transfer.failed |
transferId | string | Sim | ID do repasse (saque da subconta) |
subAccountId | string | Sim | ID da subconta de origem do repasse |
externalReference | string | Não | Referência enviada no POST /v1/sub-accounts/:id/transfer (conciliação) |
amount | number | Sim | Valor bruto debitado do saldo da subconta |
netAmount | number | Sim | Valor enviado via PIX à chave da subconta |
fee | number | Sim | Taxa do repasse |
feeChargedTo | string | Sim | De onde saiu a taxa: SUBACCOUNT (descontada do valor) ou OWNER (debitada da conta principal) |
status | string | Sim | completed ou failed |
failedReason | string | Não | Motivo — presente apenas em subaccount.transfer.failed |
endToEnd | string | Não | ID End-to-End do PIX — presente quando liquidado |
Configurando Webhooks
Crie um endpoint no seu servidor
Crie uma rota HTTP POST no seu servidor para receber as notificações. Ex: https://seusite.com/webhooks/ActivePayments
Configure no Dashboard
Acesse Configurações → Webhooks e adicione seu endpoint, selecionando os eventos que deseja receber.
Salve o Secret
Guarde o webhook_secret gerado. Você usará ele para verificar a autenticidade das chamadas.
Verifique com Ping
Use o botão "Testar" no Dashboard para enviar um evento pinge validar se seu endpoint está funcionando.
webhook_secret é configurado em Webhooks → Webhook Secret e é global para todos os endpoints do usuário. Todos os webhooks enviados para suas URLs usarão este mesmo secret para gerar a assinatura HMAC-SHA256.Verificação de Assinatura
Cada webhook inclui um header X-Webhook-Signature com uma assinatura HMAC-SHA256. Sempre verifique esta assinatura para garantir que a chamada veio do ActivePayments.
const crypto = require('crypto');
const express = require('express');
const app = express();
// IMPORTANTE: Usar express.raw() para capturar o body bruto
// A assinatura HMAC é calculada sobre o body exato enviado
app.post('/webhooks/ActivePayments',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-webhook-signature'];
const rawBody = req.body; // Buffer do body original
// Calcular HMAC sobre o body bruto (não o JSON parseado)
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
// Comparação segura contra timing attacks
const isValid = crypto.timingSafeEqual(
Buffer.from(signature || ''),
Buffer.from(expectedSignature)
);
if (!isValid) {
console.warn('Webhook rejeitado: assinatura inválida');
return res.status(401).json({ error: 'Invalid signature' });
}
// Parse do JSON após validar assinatura
const payload = JSON.parse(rawBody.toString());
const { event, data } = payload;
// Processar eventos de forma assíncrona
processWebhookAsync(event, data);
// Retornar 200 imediatamente
res.status(200).json({ received: true });
});
async function processWebhookAsync(event, data) {
// Verificar idempotência (evitar processar duplicatas)
const processed = await checkIfAlreadyProcessed(data.chargeId);
if (processed) return;
switch (event) {
case 'charge.paid':
console.log('Pagamento confirmado:', data.chargeId);
// Liberar produto/serviço
break;
case 'charge.cancelled':
console.log('Cobrança cancelada:', data.chargeId);
break;
}
}import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = 'seu_webhook_secret'
def verify_signature(payload, signature, secret):
expected = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
@app.route('/webhooks/ActivePayments', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Webhook-Signature')
payload = request.get_data(as_text=True)
if not verify_signature(payload, signature, WEBHOOK_SECRET):
return jsonify({'error': 'Invalid signature'}), 401
data = request.json
event = data['event']
if event == 'charge.paid':
charge_id = data['data']['chargeId']
print(f'Pagamento confirmado: {charge_id}')
# Liberar produto/serviço
return jsonify({'received': True}), 200Boas Práticas
- Retorne 200 rapidamente: Processe o webhook de forma assíncrona e retorne 200 imediatamente para evitar timeouts.
- Trate idempotência: O mesmo evento pode ser enviado mais de uma vez em caso de retry. Verifique se já foi processado antes de agir.
- Use HTTPS: Seu endpoint deve usar HTTPS para proteger os dados em trânsito.
- Implemente logs: Registre todos os webhooks recebidos para auditoria e debugging.
- Confirme com a API: Para transações críticas, consulte a API após receber o webhook para confirmar o status.
Política de Retry
Se seu servidor não retornar um status 2xx, o ActivePayments tentará reenviar o webhook automaticamente:
| Tentativa | Delay |
|---|---|
| 1ª | Imediato |
| 2ª | 1 minuto |
| 3ª | 5 minutos |
| 4ª (última) | 30 minutos |