Y
YayaPay Docs
Production Ready · INR & USDT Payments

YayaPay API Documentation

Complete integration reference for Pay-In (deposit) and Pay-Out (withdrawal) via UPI, Bank Transfer, and USDT TRC20.

Base URL: https://yayapay.shop
Currency: INR
Auth: user_token
New: USDT TRC20

Introduction

Welcome to the YayaPay Developer API. This gateway provides INR payment collection (Pay-In) and merchant payout (Pay-Out) via UPI, Bank Transfer, and USDT TRC20.

Pay-In requests use form-encoded (application/x-www-form-urlencoded) POST. Pay-Out requests use JSON (application/json) POST. All responses are JSON.

USDT TRC20 Payouts are now supported. Use withdraw_type: "usdt" with a valid TRC20 wallet address. Fixed rate: 1 USDT = ₹118 INR. Minimum payout: ₹118.

Pay-Out API is only available for Unlimited Merchant accounts. Please upgrade your plan in the merchant dashboard to access payout functionality.

Authentication

All API requests are authenticated using your user_token, a unique token assigned to your merchant account. Retrieve it from your merchant dashboard.

For Pay-In, pass user_token as a form field. For Pay-Out, include it in the JSON request body.

Never expose your user_token in client-side code, HTML, or public repositories. Always make API calls from your server backend only.

Pay-In (Deposit)

Create a payment order for your customer. On success, redirect the customer to the returned payment_url to complete payment.

POST https://yayapay.shop/api/create-order

Requests must use form-encoded body: Content-Type: application/x-www-form-urlencoded

1 Request Parameters
FieldTypeRequiredDescription
user_tokenstringRequiredUnique token assigned to the merchant.
customer_mobilestringRequiredCustomer's mobile number.
amountstringRequiredTransaction amount in INR.
order_idstringRequiredUnique order identifier from your system. Must not be reused.
redirect_urlstringRequiredURL where the user is redirected after payment.
remark1stringOptionalFirst remark or reference note.
remark2stringOptionalSecond remark or reference note.
2 Success Response
JSON · Success
{
  "status": true,       // true = success, false = failed
  "msg": "Order Created Successfully",
  "data": {
    "orderId": "1234561705047510",
    "payment_url": "https://yayapay.shop/pay/..."  // redirect customer here
  }
}
3 Failure Response
JSON · Failure
{
  "status": false,
  "msg": "Missing required fields"  // read msg to find out why
}

Pay-Out (Withdrawal)

Programmatically initiate payouts to your users via UPI, Bank Transfer, or USDT TRC20. Payout requests are queued as PENDING and processed after admin approval.

POST https://yayapay.shop/api/create_payout

Requests must use JSON body: Content-Type: application/json

1 Common Parameters
FieldTypeRequiredDescription
user_tokenstringRequiredYour Merchant User Token from dashboard.
payout_order_idstringRequiredUnique payout reference ID from your system. Must be unique per user.
amountfloatRequiredPayout amount in INR. Minimum ₹100 (UPI/Bank) or ₹118 (USDT). Use plain float, e.g. 500 or 500.50.
withdraw_typestringRequiredTransfer mode. Accepted: upi, bank, or usdt.
redirect_urlstringOptionalCallback URL (POST) notified after admin approval or rejection.
remark1stringOptionalCustom remark (e.g. Vendor Payment).
remark2stringOptionalAdditional remark (e.g. March Invoice).
2 UPI Transfer Fields

Required when withdraw_type is upi.

FieldTypeRequiredDescription
upi_idstringRequiredRecipient UPI ID. Format: username@bank or mobile@upi
e.g. user@paytm, 9876543210@ybl
3 Bank Transfer Fields

Required when withdraw_type is bank.

FieldTypeRequiredDescription
account_holderstringRequiredAccount holder name as per bank records.
account_numberstringRequiredBank account number (9–18 digits).
ifsc_codestringRequiredIFSC code. Format: HDFC0001234
bank_namestringRequiredName of the bank (e.g. HDFC Bank, SBI).
branch_namestringOptionalBranch name (e.g. Kolkata Main Branch).
4 🪙 USDT TRC20 Fields

Required when withdraw_type is usdt. The payout amount is entered in INR and converted to USDT at the fixed platform rate.

Fixed Rate  |  1 USDT = ₹118 INR  |  Minimum ₹118
FieldTypeRequiredDescription
trc20_address string Required Recipient USDT TRC20 wallet address.
Must start with T and be exactly 34 characters (Base58 encoded).
e.g. TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE

How USDT conversion works: You specify the INR amount (e.g. ₹1,180). The fee (3% + ₹6) is deducted from your INR balance. The requested amount is then divided by ₹118 to calculate USDT sent to the wallet.

Example: Request ₹1,180 → Fee = ₹35.40 + ₹6 = ₹41.40 → Total deducted = ₹1,221.40 → Recipient gets 10.000000 USDT

5 Fee Structure

The following fees apply to all three payout types (UPI, Bank, USDT). Fee is always calculated and deducted in INR:

Processing Fee3% of payout amount
Fixed Charge₹6.00
Total Deducted (INR)amount + (amount × 3%) + ₹6
🪙 USDT Sent to Wallet requested_amount ÷ 118 = X USDT

UPI / Bank example: ₹500 payout → Fee ₹15 + ₹6 = ₹21 → Total deducted ₹521
USDT example: ₹1,180 payout → Fee ₹35.40 + ₹6 = ₹41.40 → Total deducted ₹1,221.40 → Recipient gets 10.000000 USDT

6 Request Sample — UPI
JSON · UPI Payout
{
  "user_token":      "your_user_token_here",
  "payout_order_id": "PAYOUT_20260302_120000",
  "amount":          500,
  "withdraw_type":   "upi",
  "upi_id":          "merchant@paytm",
  "redirect_url":    "https://yoursite.com/payout-callback",
  "remark1":         "Vendor Payment",
  "remark2":         "March Invoice"
}
7 Request Sample — Bank Transfer
JSON · Bank Payout
{
  "user_token":      "your_user_token_here",
  "payout_order_id": "PAYOUT_20260302_120001",
  "amount":          1000,
  "withdraw_type":   "bank",
  "account_holder":  "Rahul Sharma",
  "account_number":  "123456789012",
  "ifsc_code":       "HDFC0001234",
  "bank_name":       "HDFC Bank",
  "branch_name":     "Kolkata Main Branch",
  "redirect_url":    "https://yoursite.com/payout-callback"
}
8 🪙 Request Sample — USDT TRC20
JSON · USDT TRC20 Payout
{
  "user_token":      "your_user_token_here",
  "payout_order_id": "PAYOUT_USDT_20260302_120002",
  "amount":          1180,             // INR amount — must be ≥ ₹118
  "withdraw_type":   "usdt",
  "trc20_address":   "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",  // 34-char TRC20 address
  "redirect_url":    "https://yoursite.com/payout-callback",
  "remark1":         "Crypto Withdrawal"
}
9 Success Response — UPI / Bank
JSON · Payout Success (UPI/Bank)
{
  "status":  true,
  "message": "Payout request created successfully",
  "result":  {
    "payout_id":               12,
    "payout_order_id":         "PAYOUT_20260302_120000",
    "withdraw_type":           "upi",
    "amount":                  500,
    "fee":                     21,
    "total_deducted":          521,
    "status":                  "PENDING",
    "available_balance_after": 4479,
    "request_date":           "2026-03-02 12:00:00"
  }
}
10 🪙 Success Response — USDT TRC20

USDT payouts include additional fields: trc20_address, usdt_amount, usdt_rate, and usdt_rate_note.

JSON · Payout Success (USDT TRC20)
{
  "status":  true,
  "message": "Payout request created successfully",
  "result":  {
    "payout_id":               13,
    "payout_order_id":         "PAYOUT_USDT_20260302_120002",
    "withdraw_type":           "usdt",
    "amount":                  1180,           // INR amount requested
    "fee":                     41.4,           // 3% (₹35.40) + ₹6
    "total_deducted":          1221.4,         // deducted from INR balance
    "status":                  "PENDING",
    "available_balance_after": 3778.6,
    "trc20_address":           "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
    "usdt_amount":             10.000000,      // USDT sent to wallet (1180 ÷ 118)
    "usdt_rate":               118,            // fixed rate used
    "usdt_rate_note":          "1 USDT = ₹118 (fixed rate)",
    "request_date":           "2026-03-02 12:00:00"
  }
}
11 Failure Response
JSON · Payout Failure
{
  "status":  false,
  "message": "Insufficient balance",
  "details": {
    "available_balance": 300.00,
    "requested_amount":  500.00,
    "fee":               21.00,
    "total_deducted":    521.00,
    // For USDT requests, these extra fields are also returned:
    "usdt_equivalent":   4.237288,   // USDT that would have been sent
    "usdt_rate":         118
  }
}

Payout Lifecycle

After submitting a payout (UPI, Bank, or USDT), it goes through the following states. A callback POST is sent to your redirect_url upon approval or rejection.

StatusMeaning
PENDING Payout submitted, awaiting admin review. Funds reserved from your balance.
APPROVED Payout approved and processed by admin. For USDT, the TRC20 transfer is dispatched to the wallet address.
REJECTED Payout rejected by admin. Balance may be restored depending on policy.

After approval or rejection, a callback POST is sent to your redirect_url. See the Payout Callback Docs for full payload details.

PHP Examples

1 Create Pay-In Order (PHP)
PHP · create_payin.php
$params = http_build_query([
    'user_token'      => 'YOUR_USER_TOKEN',
    'customer_mobile' => '9876543210',
    'amount'          => '500',
    'order_id'        => 'ORD-' . time(),
    'redirect_url'    => 'https://yoursite.com/payment-return',
    'remark1'         => 'Wallet Deposit',
]);

$ch = curl_init('https://yayapay.shop/api/create-order');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $params,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/x-www-form-urlencoded'],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 15,
    CURLOPT_SSL_VERIFYPEER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

if ($response['status'] === true) {
    header('Location: ' . $response['data']['payment_url']);
    exit;
} else {
    echo 'Pay-In Error: ' . $response['msg'];
}
2 Create Pay-Out via UPI (PHP)
PHP · create_payout_upi.php
$payload = [
    'user_token'      => 'YOUR_USER_TOKEN',
    'payout_order_id' => 'PAYOUT-' . time(),
    'amount'          => 500,
    'withdraw_type'   => 'upi',
    'upi_id'          => 'user@paytm',
    'redirect_url'    => 'https://yoursite.com/payout-callback',
    'remark1'         => 'Withdrawal',
];

$ch = curl_init('https://yayapay.shop/api/create_payout');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 15,
    CURLOPT_SSL_VERIFYPEER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

if ($response['status'] === true) {
    $result = $response['result'];
    echo 'Payout queued. ID: ' . $result['payout_id'];
    echo ' | Balance after: ₹' . $result['available_balance_after'];
} else {
    echo 'Payout Error: ' . $response['message'];
}
3 Create Pay-Out via Bank Transfer (PHP)
PHP · create_payout_bank.php
$payload = [
    'user_token'      => 'YOUR_USER_TOKEN',
    'payout_order_id' => 'PAYOUT-BANK-' . time(),
    'amount'          => 1000,
    'withdraw_type'   => 'bank',
    'account_holder'  => 'Rahul Sharma',
    'account_number'  => '123456789012',
    'ifsc_code'       => 'HDFC0001234',
    'bank_name'       => 'HDFC Bank',
    'branch_name'     => 'Mumbai Main Branch',
    'redirect_url'    => 'https://yoursite.com/payout-callback',
];

$ch = curl_init('https://yayapay.shop/api/create_payout');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 15,
    CURLOPT_SSL_VERIFYPEER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

if ($response['status'] === true) {
    echo 'Payout queued. ID: ' . $response['result']['payout_id'];
} else {
    echo 'Error: ' . $response['message'];
}
4 🪙 Create Pay-Out via USDT TRC20 (PHP)
PHP · create_payout_usdt.php
// Fixed rate: 1 USDT = ₹118
$inr_amount     = 1180;   // INR to withdraw (must be ≥ ₹118)
$trc20_address  = 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE';

// Validate TRC20 address before sending
if (!preg_match('/^T[1-9A-HJ-NP-Za-km-z]{33}$/', $trc20_address)) {
    die('Invalid TRC20 address');
}

$payload = [
    'user_token'      => 'YOUR_USER_TOKEN',
    'payout_order_id' => 'PAYOUT-USDT-' . time(),
    'amount'          => $inr_amount,
    'withdraw_type'   => 'usdt',
    'trc20_address'   => $trc20_address,
    'redirect_url'    => 'https://yoursite.com/payout-callback',
    'remark1'         => 'Crypto Withdrawal',
];

$ch = curl_init('https://yayapay.shop/api/create_payout');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 15,
    CURLOPT_SSL_VERIFYPEER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

if ($response['status'] === true) {
    $r = $response['result'];
    echo 'Payout queued. ID: '     . $r['payout_id'] . "\n";
    echo 'USDT to be sent: '       . $r['usdt_amount'] . ' USDT' . "\n";
    echo 'Wallet: '                . $r['trc20_address'] . "\n";
    echo 'INR deducted: ₹'         . $r['total_deducted'] . "\n";
    echo 'Balance remaining: ₹'    . $r['available_balance_after'];
} else {
    echo 'USDT Payout Error: ' . $response['message'];
}

Pay-In Webhook & Callback

When a customer completes a deposit, the gateway fires a server-to-server GET/POST to your webhook.php. The handler credits the user's balance, marks the order processed, then sends a signed callback POST to the callback_url you stored on the order — so your application can react in real time (update UI, send push notification, award bonus, etc.).

Callback fires after the database COMMIT — a callback delivery failure will never roll back the user's balance credit.

1 Database Setup

Add a callback_url column to your thevani (orders) table. Run this once:

SQL · Migration
ALTER TABLE recharge
  ADD COLUMN callback_url VARCHAR(500) DEFAULT NULL AFTER status;

-- Optional: idempotency table to prevent double-processing on gateway retries
CREATE TABLE processed_callbacks (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  order_id   VARCHAR(100) NOT NULL UNIQUE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
2 Saving the Callback URL on Order Creation

When you create a new deposit order in thevani, store your application's callback URL so the webhook can retrieve it later:

PHP · Save callback_url at order creation
$callbackUrl = 'https://yoursite.com/payment-callback';

$stmt = $conn->prepare(
    "INSERT INTO recharge (orderid, balance, userid, status, callback_url)
     VALUES (?, ?, ?, '0', ?)"
);
$stmt->bind_param('sdss', $orderId, $amount, $userId, $callbackUrl);
$stmt->execute();
3 Incoming Webhook Parameters

The gateway delivers these parameters to your webhook.php via GET or POST:

ParameterTypeRequiredDescription
order_idstringRequiredUnique order identifier from the gateway.
statusstringRequiredSUCCESS for a completed payment, or a failure string.
remark1stringOptionalRemark forwarded from the gateway — passed along in the callback.
4 Webhook Response Codes
ResponseScenario
ok2Order found, status=SUCCESS — balance credited successfully.
ok1Order not found, already processed, or non-SUCCESS status.
{"status":false,"message":"Missing parameters"}order_id or status was empty.
{"status":false,"message":"Transaction failed"}Database error — transaction rolled back.
5 Full webhook.php Source
PHP · webhook.php
// ── Config ──────────────────────────────────────────────────
define('DB_HOST',         'localhost');
define('DB_USERNAME',     'username');
define('DB_PASSWORD',     'password');
define('DB_NAME',         'dbname');
define('CALLBACK_SECRET', 'YOUR_SECRET_KEY_HERE'); // change this

// ── Connection ───────────────────────────────────────────────
$conn = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); }
$conn->set_charset("utf8");

// ── Callback dispatcher ──────────────────────────────────────
// Sends a signed POST to callback_url stored on the order row.
// Failure is logged — never rolls back the DB transaction.
function sendCallback(
    string $url, string $orderId, string $status,
    float  $amount, string $userId, string $remark1
): bool {
    if (empty($url)) {
        writeLog("Callback skipped: no callback_url for order $orderId");
        return false;
    }
    $ts  = time();
    $sig = hash_hmac('sha256', $orderId . $status . $amount . $ts, CALLBACK_SECRET);
    $payload = json_encode([
        'order_id'  => $orderId,
        'status'    => $status,   // "SUCCESS" or "FAILED"
        'amount'    => $amount,
        'user_id'   => $userId,
        'remark1'   => $remark1,
        'timestamp' => $ts,
        'signature' => $sig,
    ]);
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $payload,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'X-Webhook-Signature: ' . $sig,
        ],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_FOLLOWLOCATION => false,
    ]);
    $res  = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $err  = curl_error($ch);
    curl_close($ch);
    if ($err) { writeLog("Callback cURL error [$orderId]: $err"); return false; }
    writeLog("Callback [$orderId] → $url | HTTP $code | " . substr($res, 0, 200));
    return ($code >= 200 && $code < 300);
}

// ── Log raw incoming data ────────────────────────────────────
writeLog("Received: " . print_r($_GET, true) . print_r($_POST, true));

// ── Collect parameters (GET or POST) ────────────────────────
$order_id = trim($_GET['order_id'] ?? $_POST['order_id'] ?? '');
$status   = trim($_GET['status']   ?? $_POST['status']   ?? '');
$remark1  = trim($_GET['remark1']  ?? $_POST['remark1']  ?? '');

if (empty($order_id) || empty($status)) {
    writeLog("Error: Missing parameters");
    echo json_encode(['status' => false, 'message' => 'Missing parameters']);
    exit;
}

if (!$checkamt) {
    writeLog("DB Error: " . $conn->error);
    echo json_encode(['status' => false, 'message' => 'Database error']);
    exit;
}
else {
    writeLog("Notice: Order $order_id not found / already done / status=$status");
    echo "ok1";
}
$conn->close();
exit;
6 Callback Payload (Sent to Your App)

After a successful commit, your callback_url receives this signed JSON via POST:

JSON · Callback Payload
{
  "order_id":  "ORD-1234567890",
  "status":    "SUCCESS",         // "SUCCESS" or "FAILED"
  "amount":    500.00,
  "user_id":   "USR-001",
  "remark1":   "Wallet Deposit",
  "timestamp": 1743479400,        // Unix epoch
  "signature": "a3f9c1d2e4b7..."  // HMAC-SHA256 — verify this
}
FieldTypeDescription
order_idstringOriginal order ID from thevani.dharavahi
statusstringSUCCESS — balance credited. FAILED — transaction rolled back.
amountfloatAmount credited to user (INR).
user_idstringInternal user ID (balakedara).
remark1stringRemark forwarded from the gateway.
timestampintUnix epoch when callback was fired. Use for replay-attack protection.
signaturestringHMAC-SHA256 of order_id + status + amount + timestamp. Always verify.
7 Callback Receiver — payment-callback.php

Create this file at your callback_url path. It verifies the signature, prevents replay attacks, and processes the payment notification:

PHP · payment-callback.php
$body   = file_get_contents('php://input');
$data   = json_decode($body, true);
$secret = 'YOUR_SECRET_KEY_HERE';   // same as CALLBACK_SECRET in webhook.php

// 1. Replay-attack protection: reject if older than 5 minutes
if (abs(time() - ($data['timestamp'] ?? 0)) > 300) {
    http_response_code(400);
    exit('Expired timestamp');
}

// 2. Verify HMAC signature
$raw      = $data['order_id'] . $data['status'] . $data['amount'] . $data['timestamp'];
$expected = hash_hmac('sha256', $raw, $secret);
if (!hash_equals($expected, $data['signature'] ?? '')) {
    http_response_code(401);
    exit('Invalid signature');
}

// 3. Idempotency — prevent double-processing on gateway retries
$stmt = $conn->prepare("SELECT id FROM processed_callbacks WHERE order_id = ?");
$stmt->bind_param('s', $data['order_id']);
$stmt->execute();
if ($stmt->get_result()->num_rows > 0) {
    http_response_code(200);
    exit('Already processed');
}

// 4. Handle the payment result
if ($data['status'] === 'SUCCESS') {
    // ✅ send push notification, update UI, award bonus, etc.
}

// 5. Mark as processed (idempotency record)
$stmt = $conn->prepare("INSERT INTO processed_callbacks (order_id) VALUES (?)");
$stmt->bind_param('s', $data['order_id']);
$stmt->execute();

http_response_code(200);
echo 'ok';

Always use hash_equals() to compare signatures — not == or ===. This prevents timing-based signature forgery attacks.

Your callback endpoint must respond with HTTP 200 within 10 seconds. If it does not, the delivery is marked failed in logs. The user balance is not affected — it was already committed.

Payout Callback

The payout callback system gives your server two ways to track payout outcomes: automatic push via your redirect_url (called once the payout is approved or rejected), and on-demand pull via a GET status endpoint you can call anytime from your backend.

Both flows share the same endpoint file: /api/payout_callback.php. Use the GET method to poll status, and implement a POST receiver on your server to handle the automatic push.

Supply a redirect_url when creating a payout and YayaPay will automatically POST the final payload to it once approved or rejected. Use the GET Status endpoint below to proactively poll if you haven't received a callback yet.

1 Callback Payload Fields

When admin approves or rejects your payout, a JSON POST is fired to your redirect_url. The payload contains all fields below. Method-specific fields (UPI / Bank / USDT) are appended automatically based on withdraw_type.

FieldTypeDescription
eventstringpayout.approved or payout.rejected
statusstringAPPROVED or REJECTED
payout_idintegerInternal payout record ID.
payout_order_idstringYour original payout reference ID passed at creation.
user_tokenstringMerchant user token used to create the payout.
withdraw_typestringupi, bank, or usdt
amountfloatRequested payout amount in INR.
feefloatFee charged (3% + ₹6) in INR.
total_deductedfloatTotal INR deducted from balance (amount + fee).
available_balance_afterfloatRemaining merchant balance after this payout.
request_datestringDate & time payout was requested (IST).
response_datestringDate & time admin approved or rejected (IST).
admin_remarksstring|nullAdmin-entered notes. Check this on REJECTED events for the rejection reason.
remark1string|nullYour custom remark passed at payout creation.
remark2string|nullYour secondary remark passed at payout creation.
messagestringHuman-readable status string including amount and reason.
upi_idstringUPI only. The UPI ID the funds were sent to.
account_holder, account_number, ifsc_code, bank_name, branch_namestringBank only. Destination bank account details.
trc20_addressstringUSDT only. Recipient TRC20 wallet address.
usdt_amountfloatUSDT only. USDT sent (INR amount ÷ 118), 6 decimal places.
usdt_ratefloatUSDT only. Fixed rate used: 118.00
usdt_rate_notestringUSDT only. Human label: 1 USDT = ₹118
Sample Payload — APPROVED (UPI)
JSON · Callback Payload (UPI Approved)
{
  "event":                   "payout.approved",
  "status":                  "APPROVED",
  "payout_id":               5,
  "payout_order_id":         "PAYOUT_20260302_120000",
  "user_token":              "your_user_token_here",
  "withdraw_type":           "upi",
  "amount":                  500,
  "fee":                     21,
  "total_deducted":          521,
  "available_balance_after": 4479,
  "request_date":           "2026-03-02 12:00:00",
  "response_date":          "2026-03-02 12:05:00",
  "admin_remarks":          "Processed",
  "remark1":                 "Vendor Payment",
  "remark2":                 "March Invoice",
  "upi_id":                  "merchant@paytm",
  "message":                 "Your payout of ₹500.00 has been approved and processed successfully."
}
Sample Payload — REJECTED
JSON · Callback Payload (Rejected)
{
  "event":          "payout.rejected",
  "status":         "REJECTED",
  "payout_id":      6,
  "payout_order_id":"PAYOUT_20260302_130000",
  "withdraw_type":  "bank",
  "amount":         1000,
  "fee":            36,
  "total_deducted": 1036,
  "admin_remarks":  "Invalid account number",  // always check this on REJECTED
  "message":        "Your payout of ₹1000.00 has been rejected. Reason: Invalid account number"
}
🪙 Sample Payload — APPROVED (USDT TRC20)
JSON · Callback Payload (USDT Approved)
{
  "event":                   "payout.approved",
  "status":                  "APPROVED",
  "payout_id":               13,
  "payout_order_id":         "PAYOUT_USDT_20260302_120002",
  "withdraw_type":           "usdt",
  "amount":                  1180,
  "fee":                     41.4,
  "total_deducted":          1221.4,
  "available_balance_after": 8778.6,
  "request_date":           "2026-03-02 12:00:00",
  "response_date":          "2026-03-02 12:08:00",
  "remark1":                 "Crypto Withdrawal",
  "trc20_address":          "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
  "usdt_amount":            10.000000,
  "usdt_rate":              118,
  "usdt_rate_note":         "1 USDT = ₹118",
  "message":                "Your payout of ₹1180.00 (≈ 10.000000 USDT TRC20) has been approved and processed successfully."
}
2 Poll Payout Status (GET)

Query the live status of any payout from your backend at any time. Identify by payout_order_id (your reference ID) or the internal payout_id. Always pass your user_token.

GET https://yayapay.shop/api/payout_callback.php?user_token=…&payout_order_id=…
Query Parameters
ParameterTypeRequiredDescription
user_tokenstringRequiredYour merchant user token. Scopes the lookup to your account only.
payout_order_idstringRequired*Your original payout reference ID. Use this or payout_id.
payout_idintegerRequired*Internal numeric payout ID returned at creation. Use this or payout_order_id.

* Provide either payout_order_id or payout_id — at least one is required.

Request Samples
cURL · Poll by payout_order_id
curl -X GET "https://yayapay.shop/api/payout_callback.php?user_token=your_user_token_here&payout_order_id=PAYOUT_20260302_120000"
cURL · Poll by payout_id
curl -X GET "https://yayapay.shop/api/payout_callback.php?user_token=your_user_token_here&payout_id=5"
Status Response
JSON · GET Status Response
{
  "status":          true,
  "payout_id":       5,
  "payout_order_id": "PAYOUT_20260302_120000",
  "payout_status":   "APPROVED",      // PENDING | APPROVED | REJECTED
  "withdraw_type":   "upi",
  "amount":          500,
  "fee":             21,
  "total_deducted":  521,
  "request_date":   "2026-03-02 12:00:00",
  "response_date":  "2026-03-02 12:05:00",
  "admin_remarks":  "Processed",
  "remark1":        "Vendor Payment",
  "remark2":        "March Invoice",
  "upi_id":         "merchant@paytm",
  "message":        "Your payout of ₹500.00 has been approved and processed successfully."
}
3 PHP — Receiving the Callback on Your Server

Host this script at your redirect_url. It receives the incoming JSON payload, reads the status, and lets you take action — notify the user, update your DB, restore balance on rejection, etc.

PHP · payout-callback-receiver.php
<?php
// ── Read raw JSON body ───────────────────────────────────────
$raw   = file_get_contents('php://input');
$data  = json_decode($raw, true);

if (empty($data)) {
    http_response_code(400);
    exit('Invalid payload');
}

// ── Extract key fields ───────────────────────────────────────
$event           = $data['event']           ?? '';   // payout.approved | payout.rejected
$payout_status   = $data['status']          ?? '';   // APPROVED | REJECTED
$payout_id       = $data['payout_id']      ?? 0;
$payout_order_id = $data['payout_order_id'] ?? '';
$withdraw_type   = $data['withdraw_type']  ?? '';
$amount          = floatval($data['amount']  ?? 0);
$admin_remarks   = $data['admin_remarks']  ?? '';
$user_token      = $data['user_token']     ?? '';

// ── Basic validation ─────────────────────────────────────────
if (empty($payout_order_id) || empty($payout_status)) {
    http_response_code(400);
    exit('Missing required fields');
}

// ── Handle USDT-specific fields ──────────────────────────────
if ($withdraw_type === 'usdt') {
    $trc20_address = $data['trc20_address'] ?? '';
    $usdt_amount   = floatval($data['usdt_amount']   ?? 0);
    $usdt_rate     = floatval($data['usdt_rate']     ?? 118);
}

// ── Act on status ────────────────────────────────────────────
switch ($payout_status) {

    case 'APPROVED':
        // Mark payout as complete in your DB
        // Notify user, update UI, trigger next workflow step
        error_log("Payout APPROVED: $payout_order_id | ₹$amount | Type: $withdraw_type");
        break;

    case 'REJECTED':
        // Notify user with rejection reason from $admin_remarks
        // Optionally restore user balance in your app
        error_log("Payout REJECTED: $payout_order_id | Reason: $admin_remarks");
        break;

    default:
        error_log("Unknown payout status: $payout_status");
        break;
}

// ── Always respond HTTP 200 within 10 seconds ────────────────
http_response_code(200);
echo 'ok';
4 PHP — Poll Payout Status (GET)

Use this when you want to proactively fetch payout status from your backend — e.g. on a cron job, user refresh, or after a timeout waiting for the automatic callback.

PHP · poll_payout_status.php
$user_token      = 'your_user_token_here';
$payout_order_id = 'PAYOUT_20260302_120000';  // or use payout_id=5

$url = 'https://yayapay.shop/api/payout_callback.php?'
     . http_build_query([
         'user_token'      => $user_token,
         'payout_order_id' => $payout_order_id,
     ]);

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_SSL_VERIFYPEER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

if ($response['status'] === true) {
    $payout_status = $response['payout_status'];  // PENDING | APPROVED | REJECTED
    $withdraw_type = $response['withdraw_type'];

    echo "Status: $payout_status\n";
    echo "Type:   $withdraw_type\n";
    echo "Amount: ₹" . $response['amount'] . "\n";

    if ($withdraw_type === 'usdt') {
        echo "USDT: " . $response['usdt_amount'] . " to " . $response['trc20_address'] . "\n";
    }
} else {
    echo 'Error: ' . $response['message'];
}

Your callback receiver must respond HTTP 200 within 10 seconds. The callback is sent once — if your server was down or timed out, use the GET Status endpoint above to manually poll the final status instead.

Callback delivery is logged server-side to ../logs/payout_callbacks.log, recording timestamp, payout ID, target URL, and HTTP response code. Contact support with your payout_order_id if a callback was not received.

Full Working Example

A complete end-to-end example — database schema with sample data, the deposit creation flow, the webhook handler, and the callback receiver. Copy each file into your project and it is ready to test.

1 Database Schema & Sample Data

Run this SQL once to create all required tables and seed them with test data:

SQL · schema.sql

-- ── Processed callbacks (idempotency) ─────────────────────────
CREATE TABLE IF NOT EXISTS processed_callbacks (
    id         INT AUTO_INCREMENT PRIMARY KEY,
    order_id   VARCHAR(100) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- ── Sample users ──────────────────────────────────────────────
INSERT INTO balance (userid, username, balance) VALUES
    ('USR001', 'Rahul Sharma',   1500.00),
    ('USR002', 'Priya Singh',    800.50),
    ('USR003', 'Amit Verma',     0.00);

-- ── Sample pending deposit orders ─────────────────────────────
INSERT INTO recharge (orderid, balance, userid, status, callback_url) VALUES
    ('ORD-20260401-001', 500.00,  'USR001', 0, 'https://yoursite.com/payment-callback'),
    ('ORD-20260401-002', 1000.00, 'USR002', 0, 'https://yoursite.com/payment-callback'),
    ('ORD-20260401-003', 250.00,  'USR003', 0, 'https://yoursite.com/payment-callback'),
    ('ORD-20260401-004', 750.00,  'USR001', 1, 'https://yoursite.com/payment-callback'); -- already processed
2 create-order.php — Initiate a Deposit

Call this from your backend when a user clicks "Add Funds". It creates the order in your DB, then calls the YayaPay API to get the payment_url and redirects the user.

PHP · create-order.php
<?php
// ── DB config ────────────────────────────────────────────────
$conn = new mysqli('localhost', 'dbdetails', 'dbdetails', 'dbdetails');
$conn->set_charset('utf8');

// ── Order details (in real use, get these from session/POST) ──
$userId      = 'USR001';
$amount      = 500;                     // INR
$orderId     = 'ORD-' . date('Ymd') . '-' . time();
$callbackUrl = 'https://yoursite.com/payment-callback';
$redirectUrl = 'https://yoursite.com/payment-return?order_id=' . $orderId;
$mobile      = '9876543210';

// ── Save order to DB BEFORE calling gateway ───────────────────
$stmt = $conn->prepare(
    "INSERT INTO recharge (orderid, balance, userid, status, callback_url)
     VALUES (?, ?, ?, '0', ?)"
);
$stmt->bind_param('sdss', $orderId, $amount, $userId, $callbackUrl);
$stmt->execute();

// ── Call YayaPay API ─────────────────────────────────────────
$params = http_build_query([
    'user_token'      => 'YOUR_USERTOKEN_HERE',
    'customer_mobile'  => $mobile,
    'amount'           => $amount,
    'order_id'         => $orderId,
    'redirect_url'     => $redirectUrl,
    'remark1'          => 'Wallet Deposit',
]);
$ch = curl_init('https://yayapay.shop/api/create-order');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $params,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/x-www-form-urlencoded'],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 15,
    CURLOPT_SSL_VERIFYPEER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

// ── Redirect user to payment page ────────────────────────────
if ($response['status'] === true) {
    header('Location: ' . $response['data']['payment_url']);
    exit;
} else {
    echo 'Order Error: ' . $response['msg'];
}
3 webhook.php — Gateway Notification Handler

The gateway POSTs to this file after the user completes payment. It credits the wallet and fires your callback.

PHP · webhook.php
<?php
define('DB_HOST',         'localhost');
define('DB_USERNAME',     'dbdetails');
define('DB_PASSWORD',     'dbdetails');
define('DB_NAME',         'dbdetails');
define('CALLBACK_SECRET', 'ch@ng3_th1s_s3cr3t');  // must match payment-callback.php

$conn = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) { die("DB Error: " . $conn->connect_error); }
$conn->set_charset('utf8');

function writeLog(string $msg): void {
    $dir = __DIR__ . '/logs';
    if (!file_exists($dir)) mkdir($dir, 0755, true);
    file_put_contents(
        $dir . '/log_' . date('Y-m-d') . '.txt',
        '[' . date('Y-m-d H:i:s') . '] ' . $msg . "
",
        FILE_APPEND | LOCK_EX
    );
}

function sendCallback(string $url, string $orderId, string $status,
                        float $amount, string $userId, string $remark1): bool {
    if (empty($url)) { writeLog("No callback_url for $orderId"); return false; }
    $ts      = time();
    $sig     = hash_hmac('sha256', $orderId.$status.$amount.$ts, CALLBACK_SECRET);
    $payload = json_encode([
        'order_id'  => $orderId,  'status'    => $status,
        'amount'    => $amount,   'user_id'   => $userId,
        'remark1'   => $remark1,  'timestamp' => $ts,
        'signature' => $sig,
    ]);
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $payload,
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json', 'X-Webhook-Signature: '.$sig],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_SSL_VERIFYPEER => true,
    ]);
    $res  = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $err  = curl_error($ch);
    curl_close($ch);
    if ($err) { writeLog("Callback cURL error [$orderId]: $err"); return false; }
    writeLog("Callback [$orderId] HTTP $code: " . substr($res, 0, 100));
    return ($code >= 200 && $code < 300);
}

// ── Log raw input ────────────────────────────────────────────
writeLog("IN: " . json_encode(['GET' => $_GET, 'POST' => $_POST]));

$order_id = trim($_GET['order_id'] ?? $_POST['order_id'] ?? '');
$status   = trim($_GET['status']   ?? $_POST['status']   ?? '');
$remark1  = trim($_GET['remark1']  ?? $_POST['remark1']  ?? '');

if (empty($order_id) || empty($status)) {
    writeLog("Missing params");
    echo json_encode(['status' => false, 'message' => 'Missing parameters']);
    exit;
}

 else {
    writeLog("SKIP: $order_id status=$status");
    echo "ok1";
}
$conn->close();
exit;
4 payment-callback.php — Your App Receives the Callback

Place this at your callback_url. It verifies the signature, blocks replay attacks, prevents double-processing, and lets you act on the result.

PHP · payment-callback.php
<?php
$conn   = new mysqli('localhost', 'dbdetails', 'dbdetails', 'dbdetails');
$secret = 'ch@ng3_th1s_s3cr3t';   // must match CALLBACK_SECRET in webhook.php

$body = file_get_contents('php://input');
$data = json_decode($body, true);

if (!$data || !isset($data['order_id'], $data['signature'], $data['timestamp'])) {
    http_response_code(400); exit('Bad request');
}

// 1. Replay-attack check — reject if older than 5 minutes
if (abs(time() - (int)$data['timestamp']) > 300) {
    http_response_code(400); exit('Expired');
}

// 2. HMAC signature verification
$expected = hash_hmac(
    'sha256',
    $data['order_id'] . $data['status'] . $data['amount'] . $data['timestamp'],
    $secret
);
if (!hash_equals($expected, $data['signature'])) {
    http_response_code(401); exit('Invalid signature');
}

// 3. Idempotency — skip if already processed
$stmt = $conn->prepare("SELECT id FROM processed_callbacks WHERE order_id=?");
$stmt->bind_param('s', $data['order_id']);
$stmt->execute();
if ($stmt->get_result()->num_rows > 0) {
    http_response_code(200); exit('ok');
}

// 4. ✅ Handle result — add your app logic here
if ($data['status'] === 'SUCCESS') {
    /*
     * Example actions you can trigger here:
     *   - Send push notification to the user's mobile app
     *   - Update a "pending" UI state to "confirmed"
     *   - Award a first-deposit bonus
     *   - Log the transaction in your own audit table
     *   - Fire a socket event (Pusher, Soketi, etc.)
     */
    error_log("Deposit confirmed: Order {$data['order_id']} | ₹{$data['amount']} → User {$data['user_id']}");
}

// 5. Record as processed
$stmt = $conn->prepare("INSERT INTO processed_callbacks (order_id) VALUES (?)");
$stmt->bind_param('s', $data['order_id']);
$stmt->execute();

http_response_code(200);
echo 'ok';
5 Simulate a Webhook Call (Test)

Use cURL to test the webhook locally without a real gateway payment:

Shell · Test with cURL
# Test SUCCESS — credits ₹500 to USR001
curl -X POST "https://yoursite.com/webhook.php"   -d "order_id=ORD-20260401-001&status=SUCCESS&remark1=TestDeposit"

# Expected response: ok2
# USR001 balance: 1500.00 → 2000.00
# callback_url receives signed JSON POST automatically

# Test already-processed order (idempotency check)
curl -X POST "https://yoursite.com/webhook.php"   -d "order_id=ORD-20260401-001&status=SUCCESS"
# Expected response: ok1 (sthiti already = 1)

# Test FAILED status
curl -X POST "https://yoursite.com/webhook.php"   -d "order_id=ORD-20260401-002&status=FAILED"
# Expected response: ok1 (status != SUCCESS, no balance change)
6 Project File Structure
Directory Structure
your-project/
├── schema.sql              ← Run once: creates tables + seeds sample data
├── create-order.php        ← Step 1: user clicks "Add Funds" → creates order → redirects
├── webhook.php             ← Step 2: gateway POSTs here after payment
├── payment-callback.php    ← Step 3: your app receives signed callback
└── logs/
    └── log_YYYY-MM-DD.txt  ← Auto-created by writeLog()

Flow summary: User → create-order.php → YayaPay payment page → User pays → Gateway POSTs to webhook.php → Balance credited → payment-callback.php notified with signed payload.

Error Reference

When status is false, read the msg (Pay-In) or message (Pay-Out) field to diagnose the issue.

Error MessageReason & Fix
Missing required fieldsOne or more required parameters were not sent. Check all required fields.
withdraw_type must be 'upi', 'bank', or 'usdt'Invalid value for withdraw_type. Only upi, bank, or usdt accepted.
Minimum payout amount is ₹100The amount is below ₹100 for UPI/Bank. Increase to at least ₹100.
Minimum payout amount for USDT is ₹118USDT payouts require at least ₹118 (= 1 USDT at fixed rate). Increase amount to ₹118 or more.
Invalid UPI IDUPI ID format is incorrect. Use username@bank or mobile@upi.
Invalid account_numberAccount number must be 9–18 digits only.
Invalid IFSC codeIFSC must match format, e.g. HDFC0001234.
Invalid TRC20 wallet addressTRC20 address must start with T and be exactly 34 Base58 characters. Double-check the wallet address. Example: TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE
Payout Order ID already existsDuplicate payout_order_id for this user. Use a unique ID per request.
Insufficient balanceYour balance is less than amount + fee. Top up your merchant account.
Your Plan Expired. Please RenewMerchant subscription has expired. Renew your plan in the dashboard.
Payout API is only available for Unlimited Merchant accountsPlan upgrade required. Upgrade to Unlimited in the merchant dashboard.
User not foundInvalid or expired user_token. Re-check your token from the dashboard.

Never retry a payout if you receive Payout Order ID already exists. The original request may have succeeded — check your dashboard before creating a new payout.

USDT TRC20 note: Always verify the TRC20 wallet address client-side before submitting. Sending USDT to a wrong address is irreversible. The API validates the format but cannot verify address ownership.