Skip to main content

Webhooks Overview

A55 sends HTTP POST webhooks to your server whenever a payment event occurs, giving you real-time visibility into charges, refunds, and subscription changes.

Why webhooks

AspectWebhooks (push)Polling (pull)
LatencySecondsMinutes (depends on poll interval)
ReliabilityGuaranteed delivery with retriesEvents missed between intervals
Server loadEvent-driven, minimalConstant requests regardless of activity

Configuration

SettingDescription
Per-charge webhook_urlSet on each charge request to override the default
Default URLConfigured in the A55 dashboard under Settings > Webhooks
PropertyValue
MethodPOST
Content-Typeapplication/json
Timeout30 seconds
TLSHTTPS required

HMAC signature verification

Every webhook includes two headers for verification:

HeaderDescription
X-Webhook-SignatureHMAC-SHA256 hex digest of the payload
X-Webhook-TimestampUnix timestamp when the webhook was sent

Concatenate timestamp.body (dot-separated, raw bytes), compute HMAC-SHA256 with your webhook secret, compare using a timing-safe function, and reject timestamps older than 5 minutes for replay protection.

import hmac, hashlib, time
def verify_webhook(body: bytes, signature: str, timestamp: str, secret: str) -> bool:
if abs(time.time() - int(timestamp)) > 300:
return False
expected = hmac.new(
secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Verify HMAC always

Never process a webhook payload without verifying the HMAC signature. Unsigned payloads may be forged.

Retry policy

AttemptDelayCumulative time
1Immediate0
21 minute1 min
35 minutes6 min
430 minutes36 min
52 hours2 h 36 min
66 hours8 h 36 min
724 hours32 h 36 min
Your responseRetried?
------
2xxNo
4xxNo (permanent rejection)
5xxYes
Timeout (>30 s)Yes
Connection refusedYes

Idempotency

Async processing

Return 200 OK immediately after validating the signature and enqueue processing in a background job.

processed = set()
def handle_webhook(payload):
event_id = payload["charge_uuid"]
if event_id in processed:
return {"status": "already_processed"}, 200
processed.add(event_id)
queue.enqueue(process_event, payload)
return {"status": "accepted"}, 200

Event types

DomainEventTrigger
Chargescharge.confirmedPayment captured
Chargescharge.errorPayment failed or declined
Chargescharge.refundedRefund completed
Chargescharge.chargebackChargeback opened
PIXpix.confirmedPIX payment received
PIXpix.expiredQR code expired
Boletoboleto.confirmedBoleto paid
Boletoboleto.expiredPast due date
Subscriptionssubscription.createdNew subscription started
Subscriptionssubscription.cancelledSubscription cancelled
Subscriptionssubscription.charge_failedRecurring charge failed

Testing

ToolUsage
webhook.siteInspect payloads without running a server
ngrokExpose local server for live sandbox webhooks
A55 sandboxSend test events from Settings > Webhooks > Test