OxaPay Docs
Legacy
Legacy
  • Introduction
  • Getting Started
  • Integrations
    • Payment Links
    • Donations
    • Merchant API
    • Payout API
    • Plugins
      • WooCommerce
      • WISECP
      • Clientexec
      • Blesta
      • WHMCS
      • PrestaShop
      • Easy Digital Downloads
      • Paid Memberships Pro
      • Gravity Forms
      • Restrict Content Pro
    • Merchant and Payout Service with API
  • API Reference
    • Creating an Invoice
    • Creating White-Label Payment
    • Creating Static Wallet
    • Revoking Static Wallet
    • Static Wallets List
    • Payment Information
    • Payment History
    • Accepted Coins
    • Price
    • Creating Payout
    • Payout Information
    • Payout History
    • Account Balance
    • Exchange Rate
    • Exchange Calculate
    • Exchange Pairs
    • Exchange Request
    • Exchange History
    • Supported Currencies
    • Supported Fiat Currencies
    • Supported Networks
    • System Status
    • Result code table
    • Merchant status table
    • Payout status table
  • Webhook
  • Use Cases
  • Troubleshooting
Powered by GitBook
On this page
  • Validating Callbacks
  • Testing Webhook
  • Sample Payment IPN data
  • Sample Payout IPN data
  • Example codes

Webhook

PreviousPayout status tableNextUse Cases

Last updated 10 days ago

Merchants can set up a callbackUrl in their merchant requests, and upon processing payments, OxaPay will send you notifications in JSON format with all required information about the transaction, such as the amount and status, when the payment status is changed to Waiting, Confirming, Paid, Failed, or Expired.

To receive webhook notifications, first, you should implement a standard HTTP POST (application/json) call over an HTTPS URL to a CGI script on your server. Insert your created endpoint address in the callbackUrl field of merchant requests. You will receive payment updates (statuses) to this URL address.

However, you will initially receive a callback with the "Confirming" status. You should wait for a second callback where the "status" value will be "Paid". When we confirm the transaction, we are ready to manage all the risks concerning accepting real funds later. You should not be concerned about it.

Merchant's callbackUrl must return an HTTP Response 200 with content: ok for OxaPay API to confirm the callback as successful. The system will try to deliver a webhook notification up to 5 times or until a successful delivery occurs, whichever happens first. If the first attempt fails, the second one is triggered after approximately 1 minute. The third one is delayed for 3 more minutes, the fourth for 30 minutes, and the last one for 3 hours.

The payout webhook is similar to a merchant webhook exactly. But for withdrawals, you will receive different values of the "address" parameter in every callback.

Validating Callbacks

Merchants must validate the signature of callbacks coming from OxaPay using their MERCHANT_API_KEY to prevent fraudulent activity. also for payout webhooks you must validate the signature using your PAYOUT_API_KEY. OxaPay uses your MERCHANT_API_KEY as the HMAC shared secret key to generate an HMAC(sha512) signature of the raw POST data. The HMAC signature is sent as an HTTP header called HMAC. The body of the request contains the callback data in JSON format, similar to the Payment Information API response body.

Testing Webhook

For payment callback handling, you can use tools like requestcatcher.com or to create a test callbackUrl and inspect the callback data sent from OxaPay. Note that OxaPay payment callbacks will not be sent to private networks (e.g., localhost).

For local debugging, you can use services like https://ngrok.io to expose your local server to the internet and receive webhook callbacks.

To utilize the payment webhook, please follow these steps:

1. Create a web server and define an endpoint to handle POST requests.

- Ensure that the firewall software on your server (e.g., Cloudflare) allows incoming requests from OxaPay. You may need to whitelist OxaPay's IP addresses on your side. Please reach out to support@oxapay.com to obtain the list of IP addresses.

2. Configure your server to receive POST requests at the specified endpoint URL.

- The POST request body will contain the necessary parameters sent by OxaPay in JSON format.

3. Validate the HMAC signature of the request to ensure the authenticity of the callback.

- Use your API_SECRET_KEY to calculate the HMAC signature and compare it with the received HMAC header.

4. Process the callback data accordingly based on the status and other parameters provided.

Sample Payment IPN data

In this section, we will provide examples of the data OxaPay send to your system at various stages of the payment process.

// Waiting IPN 
// Payer selected the payment currency and geting address. Awaiting payment.
{
    "status":"Waiting",
    "trackId":"35092972",
    "amount":"100",
    "currency":"TRX",
    "feePaidByPayer":0,
    "underPaidCover":2,
    "email":"",
    "orderId":"665673996",
    "description":"",
    "date":"1698107946",
    "payDate":0,
    "type":"payment"
}
// Confirming IPN
// Payer transferred the payment for the invoice. Awaiting blockchain network confirmation.
{
    "status":"Confirming",
    "trackId":"35092972",
    "address":"TCue3HjMWfARcf1W5NK7MEqLyLcWtmbZcx",
    "senderAddress":"TH7Q7AVjwXRbC6hnJua8MFyfvkVnoyeHB6",
    "txID":"918341f6c280b3b4ca2f8e50d8a32054a20cc999fd94cfa5da6d1b9f0b3f9a1e",
    "amount":"100",
    "currency":"TRX",
    "price":"9.23",
    "payAmount":"14",
    "payCurrency":"TRX",
    "network":"TRC20",
    "feePaidByPayer":0,
    "underPaidCover":2,
    "email":"",
    "orderId":"665673996",
    "description":"","date":"1698156969",
    "payDate":"1698160515",
    "type":"payment"
}
// Paid IPN
// Payment is confirmed by the network and has been credited to the merchant. Purchased goods/services can be safely delivered to the payer.
{
    "status":"Paid",
    "trackId":"35092972",
    "address":"TCue3HjMWfARcf1W5NK7MEqLyLcWtmbZcx",
    "senderAddress":"TH7Q7AVjwXRbC6hnJua8MFyfvkVnoyeHB6",
    "txID":"918341f6c280b3b4ca2f8e50d8a32054a20cc999fd94cfa5da6d1b9f0b3f9a1e",
    "amount":"100",
    "currency":"TRX",
    "price":"9.23",
    "payAmount":"100",
    "payCurrency":"TRX",
    "network":"TRC20",
    "feePaidByPayer":0,
    "underPaidCover":2,
    "email":"",
    "orderId":"665673996",
    "description":"",
    "date":"1698156969",
    "payDate":"1698160515",
    "type":"payment"
}

Sample Payout IPN data

In this section, we will provide examples of the data OxaPay send to your system at various stages of the payout process.

// Confirming IPN
// You payout request sent and awaiting blockchain network confirmation.
{
    "status": "Confirming",
    "trackId": "28156600",
    "txID": "x",
    "address": "x",
    "amount": "200",
    "currency": "DGB",
    "price": "1.53600000",
    "network": "DigiByte",
    "date": "1700847655",
    "description": "",
    "type": "payout"
}
// Complete IPN
// Payout is confirmed by the network.
{
    "status": "Complete",
    "trackId": "28156600",
    "txID": "x",
    "address": "x",
    "amount": "200",
    "currency": "DGB",
    "price": "1.53600000",
    "network": "DigiByte",
    "date": "1700847655",
    "description": "",
    "type": "payout"
}

Example codes

<?php
// Get the request data
$postData = file_get_contents('php://input');
$data = json_decode($postData, true);

// Validate HMAC signature
if ($data['type'] === 'payment') {
    $apiSecretKey = 'YOUR_MERCHANT_API_KEY';
} elseif ($data['type'] === 'payout') {
    $apiSecretKey = 'YOUR_PAYOUT_API_KEY';
} else {
    http_response_code(400);
    echo 'Invalid data.type';
    exit;
}

$hmacHeader = $_SERVER['HTTP_HMAC'];
$calculatedHmac = hash_hmac('sha512', $postData, $apiSecretKey);

if ($calculatedHmac === $hmacHeader) {
    // HMAC signature is valid
    // Process the callback data
    if ($data['type'] === 'payment') {
        echo 'Received payment callback: ' . json_encode($data);
        // Process payment data here
    } elseif ($data['type'] === 'payout') {
        echo 'Received payout callback: ' . json_encode($data);
        // Process payout data here
    }

    // Return HTTP Response 200 with content "OK"
    http_response_code(200);
    echo 'OK';
} else {
    // HMAC signature is not valid
    // Handle the error accordingly
    http_response_code(400);
    echo 'Invalid HMAC signature';
}
?>
const http = require('http');
const crypto = require('crypto');

const server = http.createServer((req, res) => {
    if (req.url === '/callback' && req.method === 'POST') {
        // Validate HMAC signature
        let postData = '';

        req.on('data', chunk => {
            postData += chunk;
        });

        req.on('end', () => {
            // Parse the JSON data
            let data = null;
            try {
                data = JSON.parse(postData);
            } catch (error) {
                res.statusCode = 400;
                res.end('Invalid JSON data');
                return;
            }

            const apiSecretKey = (data.type === 'payment') ? 'YOUR_MERCHANT_API_KEY' : 'YOUR_PAYOUT_API_KEY';
            const hmacHeader = req.headers['hmac'];
            const calculatedHmac = crypto
                .createHmac('sha512', apiSecretKey)
                .update(postData)
                .digest('hex');

            if (calculatedHmac === hmacHeader) {
                // HMAC signature is valid
                // Process the callback data based on the type
                if (data.type === 'payment') {
                    console.log('Received payment callback:', data);
                    // Process payment data here
                } else if (data.type === 'payout') {
                    console.log('Received payout callback:', data);
                    // Process payout data here
                }

                // Return HTTP Response 200 with content "OK"
                res.statusCode = 200;
                res.end('OK');
            } else {
                // HMAC signature is not valid
                // Handle the error accordingly
                res.statusCode = 400;
                res.end('Invalid HMAC signature');
            }
        });
    } else {
        // Invalid path or method
        res.statusCode = 404;
        res.end('Not Found');
    }
});

server.listen(3000, () => {
    console.log('Server listening on port 3000');
});
from flask import Flask, request
import hmac
import hashlib
import json

app = Flask(__name__)

@app.route('/callback', methods=['POST'])
def handle_callback():
    post_data = request.get_data(as_text=True)
    data = json.loads(post_data)

    # Validate HMAC signature
    if data['type'] == 'payment':
        api_secret_key = 'YOUR_MERCHANT_API_KEY'
    elif data['type'] == 'payout':
        api_secret_key = 'YOUR_PAYOUT_API_KEY'
    else:
        return 'Invalid data.type', 400
    hmac_header = request.headers.get('HMAC')
    post_data = request.get_data()
    calculated_hmac = hmac.new(api_secret_key.encode(), post_data, hashlib.sha512).hexdigest()

    if calculated_hmac == hmac_header:
        # HMAC signature is valid
        # Process the callback data
        if data['type'] == 'payment':
            print('Received payment callback:', data)
            # Process payment data here
        elif data['type'] == 'payout':
            print('Received payout callback:', data)
            # Process payout data here
            # Return HTTP Response 200 with content "OK"
        return 'OK', 200
    else:
        # HMAC signature is not valid
        # Handle the error accordingly
        return 'Invalid HMAC signature', 400


if __name__ == '__main__':
    app.run(host='YOUR_HOST_ADDRESS',port=3000)

Again, please note that these code snippets serve as examples and may require modifications based on your specific implementation and framework.

webhook.site