Payout webhook
A55 sends a POST to the webhook_url you set on a payout whenever its status changes. The payload is intentionally minimal — use payout_uuid to fetch the full payout from Get payout.
The webhook_url is provided per payout in the Create payout request body. Private and localhost URLs are rejected.
Payload fields
| Field | Type | Description |
|---|---|---|
payout_uuid | UUID | Unique payout identifier |
status | string | New payout status (see table below) |
transaction_reference | string | Idempotency key sent on creation |
external_id | string | Provider's payout id |
type_payout | string | Payout rail (pix, bank_transfer, ted, spei, cash_pickup) |
payment_code | string or null | Cash pickup code (cash payouts only) |
authorization_code | string or null | Provider authorization code, when available |
Payload example
{
"payout_uuid": "9b1f0c88-3a3c-4f2f-9d6e-1f0a2d4e88c1",
"status": "realized",
"transaction_reference": "PAY-2048",
"external_id": "MN-20260616-0001",
"type_payout": "pix",
"payment_code": null,
"authorization_code": "AUTH-55812"
}
All possible statuses
| Status | Description |
|---|---|
pending | Created, awaiting processing |
issued | Sent to the provider |
realized | Confirmed and settled |
returned | Returned by the receiving bank |
canceled | Canceled |
error | Failed during processing |
expired | Expired |
Fetching full payout details
The webhook payload is minimal by design. After receiving it, call the API with your Bearer token to get the complete payout object:
curl "https://api.a55.tech/api/v1/bank/wallet/payout/{payout_uuid}/{wallet_uuid}/" \
-H "Authorization: Bearer $ACCESS_TOKEN"
Webhook is the trigger, API is the source of truth
Use the webhook to know when a payout changed, then call GET /api/v1/bank/wallet/payout/{payout_uuid}/{wallet_uuid}/ to get full details such as fee, confirmed_date, and destination.
Handler examples
- Python (Flask)
- JavaScript (Express)
from flask import Flask, request, jsonify
app = Flask(__name__)
processed = set()
@app.route("/webhooks/a55/payouts", methods=["POST"])
def payout_webhook():
data = request.get_json()
payout_uuid = data.get("payout_uuid")
status = data.get("status")
# Deduplicate
if payout_uuid in processed:
return jsonify(status="duplicate"), 200
processed.add(payout_uuid)
if status == "realized":
mark_payout_settled(payout_uuid)
elif status in ("returned", "error", "expired"):
flag_payout_failure(payout_uuid, status)
return jsonify(status="accepted"), 200
const express = require("express");
const app = express();
app.use(express.json());
const processed = new Set();
app.post("/webhooks/a55/payouts", (req, res) => {
const { payout_uuid, status } = req.body;
// Respond immediately
res.sendStatus(200);
// Deduplicate
if (processed.has(payout_uuid)) return;
processed.add(payout_uuid);
if (status === "realized") {
markPayoutSettled(payout_uuid);
} else if (["returned", "error", "expired"].includes(status)) {
flagPayoutFailure(payout_uuid, status);
}
});
Best practices
| Practice | Why |
|---|---|
Return 200 immediately | Avoids timeouts and duplicate deliveries |
| Process in background | Keeps response time low |
Deduplicate by payout_uuid | Handles retried webhooks safely |
| Fetch full details via API | Webhook payload is minimal by design |
| Use a public, reachable URL | Private/localhost URLs are rejected |