Postback Data
When you set a postback URL, after each transaction we’ll send a POST request with a JSON body in the following shape:
{
"postback_secret": "xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx",
"postback_type": "transaction",
"tx_id": 67890,
"tx_hash": "abcdef1234567890",
"tx_type": "invoice",
"label": "Order #1234",
"currency": "USDTBEP20",
"amount": 1000.00,
"amount_usd": 1000.00,
"network_fee": 0.00,
"network_fee_usd": 0.00,
"service_fee": 5.00,
"service_fee_usd": 5.00,
"received_amount": 500.00,
"received_amount_usd": 500.00,
"status": "paid",
"created_at": "2025-05-24T15:00:00Z"
}Note that received_amount may be less than amount if the client has not paid the entire amount at once. For automatic reconciliation of the payment status, see the is_paid field.
Field Reference
postback_secret
string
Your postback secret from profile page
postback_type
string
Postback type (transaction, wallet_expired, invoice_expired)
tx_id
int
Internal transaction ID in our system
tx_hash
string
On-chain transaction hash
tx_type
string
invoice (payment to an invoice) or wallet (direct wallet transfer)
label
string
Your custom label passed in when creating the invoice or wallet
currency
string
Currency code; here USDTBEP20
amount
float
Amount in the original currency (before fees)
amount_usd
float
USD equivalent at the time of invoice creation (for stablecoins this will typically match)
network_fee
float
Network fee of a currency
network_fee_usd
float
USD equivalent of the network fee
service_fee
float
Our service fee
service_fee_usd
float
USD equivalent of the service fee
received_amount
float
Amount received for all payments per invoice (For invoices only)
received_amount_usd
float
USD equivalent of the net received amount
status
string
Transaction status. Possible values: paid, partially_paid
created_at
string
ISO 8601 timestamp when the transaction was created
Example
Flask
from flask import Flask, request, jsonify, abort
import os
app = Flask(__name__)
SHARED_SECRET = os.environ.get("POSTBACK_SECRET")
@app.route('/postback', methods=['POST'])
def postback():
data = request.get_json()
# Verify that the incoming secret matches
if data.get("postback_secret") != SHARED_SECRET:
abort(401)
# Remove secret before further processing
data.pop("postback_secret", None)
# TODO: handle the transaction data
# e.g. save to database, update order status, etc.
return jsonify({"status": "ok"}), 200FastAPI
from fastapi import FastAPI, Request, HTTPException
import os
app = FastAPI()
SHARED_SECRET = os.getenv("POSTBACK_SECRET")
@app.post("/postback")
async def postback(request: Request):
payload = await request.json()
# verify the shared secret
if payload.get("postback_secret") != SHARED_SECRET:
raise HTTPException(status_code=401, detail="Unauthorized")
# remove secret before processing
payload.pop("postback_secret", None)
# TODO: handle transaction data
return {"status": "ok"}Django
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('postback/', views.postback),
]
# views.py
import os, json
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
SHARED_SECRET = os.getenv("POSTBACK_SECRET")
@csrf_exempt
def postback(request):
if request.method != "POST":
return HttpResponse(status=405)
try:
payload = json.loads(request.body)
except json.JSONDecodeError:
return HttpResponse(status=400)
# verify the shared secret
if payload.get("postback_secret") != SHARED_SECRET:
return HttpResponse(status=401)
# remove secret
payload.pop("postback_secret", None)
# TODO: handle transaction data
return JsonResponse({"status": "ok"})php://input
<?php
// /postback.php
define('SHARED_SECRET', getenv('POSTBACK_SECRET'));
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
// Verify that the incoming secret matches
if (!isset($data['postback_secret']) || $data['postback_secret'] !== SHARED_SECRET) {
http_response_code(401);
exit;
}
// Remove secret before further processing
unset($data['postback_secret']);
// TODO: handle the transaction data
// e.g. insert into database, mark invoice paid, etc.
http_response_code(200);
echo json_encode(['status' => 'ok']);Laravel
// routes/api.php
Route::post('/postback', 'PostbackController@handle');
// app/Http/Controllers/PostbackController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PostbackController extends Controller
{
public function handle(Request $request)
{
$secret = $request->input('postback_secret');
// verify the shared secret
if ($secret !== env('POSTBACK_SECRET')) {
return response()->json(['message' => 'Unauthorized'], 401);
}
// remove secret from payload
$data = $request->except('postback_secret');
// TODO: handle $data transaction
return response()->json(['status' => 'ok']);
}
}const http = require('http');
const SHARED_SECRET = process.env.POSTBACK_SECRET;
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/postback') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
let data;
try {
data = JSON.parse(body);
} catch {
res.writeHead(400);
return res.end();
}
// Verify that the incoming secret matches
if (data.postback_secret !== SHARED_SECRET) {
res.writeHead(401);
return res.end();
}
// Remove secret before further processing
delete data.postback_secret;
// TODO: handle the transaction data
// e.g. update your records, trigger business logic, etc.
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok' }));
});
} else {
res.writeHead(404);
res.end();
}
});
server.listen(8000, () => console.log('Listening on port 8000'));const express = require('express');
const app = express();
const SHARED_SECRET = process.env.POSTBACK_SECRET;
app.use(express.json());
app.post('/postback', (req, res) => {
const data = req.body;
// Verify that the incoming secret matches
if (data.postback_secret !== SHARED_SECRET) {
return res.sendStatus(401);
}
// Remove secret before further processing
delete data.postback_secret;
// TODO: handle the transaction data
// e.g. mark invoice as paid, send notification, etc.
res.json({ status: 'ok' });
});
app.listen(8000, () => {
console.log('Server running on http://localhost:8000');
});# config/routes.rb
Rails.application.routes.draw do
post '/postback', to: 'postbacks#create'
end
# app/controllers/postbacks_controller.rb
class PostbacksController < ApplicationController
skip_before_action :verify_authenticity_token
def create
secret = params.delete(:postback_secret)
# verify the shared secret
return head :unauthorized unless secret == ENV['POSTBACK_SECRET']
# params now holds your transaction data
# TODO: handle params (tx_hash, is_paid, etc.)
render json: { status: 'ok' }
end
endpackage main
import (
"github.com/gin-gonic/gin"
"net/http"
"os"
)
func main() {
r := gin.Default()
shared := os.Getenv("POSTBACK_SECRET")
r.POST("/postback", func(c *gin.Context) {
var payload map[string]interface{}
if err := c.ShouldBindJSON(&payload); err != nil {
c.Status(http.StatusBadRequest)
return
}
// verify the shared secret
if ps, ok := payload["postback_secret"].(string); !ok || ps != shared {
c.Status(http.StatusUnauthorized)
return
}
// remove the secret
delete(payload, "postback_secret")
// TODO: process payload (tx_id, received_amount, etc.)
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
r.Run(":8000")
}Last updated