This guide provides comprehensive documentation for integrating with the FundPay payment platform. Follow the steps below to implement a secure and reliable integration.
The following sequence diagrams illustrate the complete integration flows for deposit and withdrawal operations. These diagrams show the interaction between your application, FundPay APIs, and the banking systems. Click on any diagram to view it in full size.
This diagram shows the complete flow for processing deposit transactions, including signature generation, API calls, and webhook notifications.
This diagram illustrates the withdrawal process, including the additional security measures and bank account verification steps required for fund disbursements.
FundPay provides two methods of authentication for API requests. Choose the method that best fits your integration needs.
Use API keys for simple authentication. Include your API key in the request header.
x-api-key: YOUR_API_KEY
Pros: Simple to implement, minimal setup required.
Cons: Less secure than signature-based authentication.
Generate a cryptographic signature for each request for enhanced security.
{ ... "signature": "GENERATED_SIGNATURE" }
Pros: Highly secure, prevents tampering and replay attacks.
Cons: More complex to implement.
We strongly recommend using signature-based authentication for production environments. API key authentication is suitable for testing and development but provides less security for live transactions.
Your API keys are available in your merchant dashboard. Each API key has specific permissions and can be restricted to certain IP addresses for additional security.
Never share your API keys or include them in client-side code. Always store API keys securely in environment variables or a secure key management system.
// JavaScript Example
const axios = require('axios');
async function makeApiRequest() {
try {
const response = await axios.get('https://api.fundpay24h.com/api/v1/merchant/balance', {
headers: {
'x-api-key': 'YOUR_API_KEY'
}
});
console.log(response.data);
} catch (error) {
console.error('Error:', error);
}
}
Generating a valid signature for your API requests involves creating an HMAC-SHA256 hash of your request parameters using your secret key.
Collect all the parameters for your API request (excluding the signature itself).
Sort all parameter keys in alphabetical order (A-Z).
Create a URL-encoded string of key-value pairs in the format: key1=value1&key2=value2
Create an HMAC-SHA256 hash of the URL-encoded string using your merchant secret key.
Include the generated signature in your API request as the signature
parameter.
When generating signatures, nested objects in the API request must be flattened into individual fields. For example:
Original nested object:
"source_bank_account": { "bank_code": "kbank", "account_name": "TEST HEELO", "account_number": "1234567" }
Flattened for signature generation:
"source_account_bank_code": "kbank" "source_account_name": "TEST HEELO" "source_account_no": "1234567"
{ "merchant_id": "d0bf1184-c1c9-4101-b034-97e3b44edf4e", "amount": "1000", "currency": "THB", "reference": "ttt", "payment_method": "withdrawal", "frontend_return_url": "https://example.com/return", "backend_return_url": "https://example.com/webhook", "source_account_bank_code": "kbank", "source_account_name": "TEST HEELO", "source_account_no": "1234567", "timestamp": "2025-07-29T21:23:29+07:00" }
YOUR_SECRET_KEY
amount=3269&backend_return_url=https%3A%2F%2Fexample.com%2Fwebhook¤cy=THB&frontend_return_url=https%3A%2F%2Fexample.com%2Freturn&merchant_id=79703311-ab77-4f93-a8b8-70c21a2c8c79&payment_method=qr_payment&reference=deposit_281753758186604&source_account_bank_code=kbank&source_account_name=TEST+HEELO&source_account_no=1234567×tamp=2025-07-29T22%3A10%3A00%2B07%3A00
7dd83af0dcae52d6f2ae5e8672f41fc067c1d587a9ab932495c38dca2e70e1e5
Below are code samples for generating signatures in different programming languages. You can also use the interactive form to generate a signature for your API request.
// JavaScript Example (Node.js)
const crypto = require('crypto');
function generateSignature(payload, secretKey) {
// Sort keys alphabetically
const sortedKeys = Object.keys(payload).sort();
// Create values object
const values = {};
for (const key of sortedKeys) {
values[key] = String(payload[key]);
}
// Create string to sign (URL-encoded key-value pairs)
const params = new URLSearchParams();
for (const key of sortedKeys) {
params.append(key, values[key]);
}
const stringToSign = params.toString();
// Generate HMAC
const hmac = crypto.createHmac('sha256', secretKey);
hmac.update(stringToSign);
return hmac.digest('hex');
}
// Example usage for withdrawal
const payload = {
merchant_id: '79703311-ab77-4f93-a8b8-70c21a2c8c79',
amount: 1000,
currency: 'THB',
reference: 'ttt',
payment_method: 'withdrawal',
frontend_return_url: 'https://example.com/return',
backend_return_url: 'https://example.com/webhook',
source_account_bank_code: 'kbank',
source_account_name: 'TEST HEELO',
source_account_no: '1234567',
timestamp: '2025-07-29T21:23:29+07:00'
};
const secretKey = 'YOUR_SECRET_KEY';
const signature = generateSignature(payload, secretKey);
When generating signatures, nested objects in the API request must be flattened into individual fields. For example:
Original nested object:
"source_bank_account": { "bank_code": "kbank", "account_name": "TEST HEELO", "account_number": "1234567" }
Flattened for signature generation:
"source_account_bank_code": "kbank" "source_account_name": "TEST HEELO" "source_account_no": "1234567"
Add this signature to your request payload in the signature
field.
FundPay provides a comprehensive set of API endpoints for managing deposits and withdrawals. Below are the detailed specifications for each endpoint.
Creates a new deposit request for a merchant. Supports both QR code and bank transfer payment methods.
Field | Type | Description |
---|---|---|
merchant_id | string | Your merchant ID provided by FundPay |
amount | number | Amount in the smallest currency unit |
currency | string | Currency code (e.g., THB) |
reference | string | Your unique reference for this transaction |
payment_method | string | Payment method (qr_payment or bank_transfer) |
frontend_return_url | string | URL to redirect customer after payment |
backend_return_url | string | URL for webhook notifications |
timestamp | string | ISO 8601 timestamp |
source_bank_account | object | The source bank account details (optional) |
destination_bank_account | object | The destination bank account details (optional) |
customer_email | string | Customer Email (optional) |
customer_phone_number | string | Customer phone number (optional) |
description | string | Additional details about the deposit (optional) |
signature | string | HMAC-SHA256 signature of the request |
{ "merchant_id": "MERCHANT_DEV_20250522_ABCDEFGHIJKLMN", "amount": 123124, "currency": "THB", "reference": "ORD1026761", "payment_method": "bank_transfer", "frontend_return_url": "https://localhost:3000/frontend", "backend_return_url": "https://localhost:3000/backend", "source_bank_account": { "bank_code": "kbank", "account_number": "12345678", "account_name": "John Doe" }, "timestamp": "2025-05-20T16:10:02.941Z", "customer_email": "[email protected]", "customer_phone_number": "0812345678", "description": "Payment for order ORD1026761", "signature": "87df2dcd4fbfe782ba9035635ea98a1fc9a4c74ff741e3fcf526ce3711940414" }
Field | Type | Description |
---|---|---|
code | integer | Response code (2000 for success) |
message | string | Response message |
data.id | string | Unique deposit ID |
data.web_payment_url | string | URL for payment page |
data.payment_token | string | Token to identify this payment |
data.total_amount | number | Total amount to be paid including any fees |
data.destination_bank_account | object | The destination bank account details |
data.destination_bank | object | Destination bank information |
data.expires_at | string | Expiration timestamp |
{ "code": 2000, "message": "Deposit created successfully", "data": { "id": "deposit_dev_ERKWB1VFRJ7TjBpHcabGaOpgr6AFpNOn", "web_payment_url": "https://customer-dev.fundpay.app/payment/EV0ZZMILAKMX5iSMqZKPXjHBeUQoA2k6C4ST7Gn7DEz0CR26KD4gWCADJD5TFK8m", "payment_token": "EV0ZZMILAKMX5iSMqZKPXjHBeUQoA2k6C4ST7Gn7DEz0CR26KD4gWCADJD5TFK8m", "total_amount": 123124.37, "expires_at": "2025-05-21T16:09:32.305746742Z", "destination_bank_account": { "bank_code": "kbank", "account_number": "12345667", "account_name": "John Doe", "account_promptpay": "1234567890123" }, "destination_bank": { "code": "004", "name": { "th": "ธนาคารกสิกรไทย", "en": "Kasikorn Bank" }, "logo_url": "https://fundpay24h.com/bank-logo/kbank.png" } } }
Retrieves the current status of a deposit by its ID.
Parameter | Type | Description |
---|---|---|
id | string | Deposit ID to retrieve |
Field | Type | Description |
---|---|---|
code | integer | Response code (2000 for success) |
message | string | Response message |
data.id | string | Unique deposit ID |
data.status | string | Deposit status (e.g., pending_approve, completed, failed) |
data.total_amount | number | Total deposit amount |
data.currency | string | Currency code |
data.reference | string | Merchant reference |
data.created_at | string | Creation timestamp |
data.updated_at | string | Last update timestamp |
data.payment_method | string | Payment method used |
data.source_bank_account | object | Source bank account details |
data.destination_bank_account | object | Destination bank account details |
data.source_bank | object | Source bank information |
data.destination_bank | object | Destination bank information |
data.merchant | object | Merchant information |
{ "code": 2000, "message": "Get deposit successfully", "data": { "id": "deposit_dev_ERKWB1VFRJ7TjBpHcabGaOpgr6AFpNOn", "status": "pending_approve", "total_amount": 123123.1, "currency": "THB", "reference": "ORD1026761", "payment_method": "bank_transfer", "created_at": "2025-05-20T17:03:40.401157Z", "updated_at": "2025-05-20T17:03:40.401157Z", "source_bank_account": { "bank_code": "kbank", "account_number": "12345678", "account_name": "John Doe" }, "destination_bank_account": { "bank_code": "kbank", "account_number": "87654321", "account_name": "Jane Smith" }, "source_bank": { "code": "004", "name": { "th": "ธนาคารกสิกรไทย", "en": "Kasikorn Bank" }, "logo_url": "https://fundpay24h.com/bank-logo/kbank.png" }, "destination_bank": { "code": "004", "name": { "th": "ธนาคารกสิกรไทย", "en": "Kasikorn Bank" }, "logo_url": "https://fundpay24h.com/bank-logo/kbank.png" }, "merchant": { "merchant_code": "MERCHANT_DEV_20250522", "legal_name": { "th": "บริษัท เทสต์ จำกัด", "en": "Test Company Limited" } } } }
Follow these security best practices to ensure your integration with FundPay is secure and reliable:
If you have any questions or need assistance with your integration, please contact our support team at [email protected].
FundPay sends webhook notifications to your backend server when transaction statuses are updated. This guide explains how to properly handle these webhook notifications.
Webhooks are HTTP callbacks that notify your system when events occur in the FundPay platform. When a transaction status changes, FundPay will send a POST request to your specified backend URL with details about the event.
When a deposit status changes, FundPay will send a webhook notification to your backend server. The webhook contains all the necessary information about the deposit transaction.
{
"amount": 1500.23,
"status": "approved",
"signature": "e666fcb596d6213b9807930909b77f37a2733d2e6a0835d97c37837b4e10f01e",
"merchant_id": "c513667a-36c5-4c2a-bbba-e72e632aa906",
"processed_by": "[email protected]",
"reference_id": "ORD-12345",
"transaction_id": "deposit_dev_EWuWJFgxR0NlrZFoJEm42ZOl3DHVfTL4",
"transaction_date": "2025-06-21T12:42:20Z",
"transaction_type": "deposit"
}
Field | Type | Description |
---|---|---|
amount | number | The transaction amount in the smallest currency unit |
status | string | Transaction status (approved, rejected, pending) |
signature | string | HMAC-SHA256 signature for verifying the webhook |
merchant_id | string | Your merchant ID |
processed_by | string | Email of the admin who processed the transaction |
reference_id | string | Your reference ID for this transaction |
transaction_id | string | FundPay's unique transaction ID |
transaction_date | string | ISO 8601 timestamp of the transaction |
transaction_type | string | Type of transaction (deposit) |
When a withdrawal status changes, FundPay will send a webhook notification to your backend server. The webhook contains detailed information about the withdrawal transaction, including a signature for verification.
{
"amount": 1000,
"remark": "my note",
"status": "rejected",
"signature": "858dfd034ef7b40298a8c69fea6961b74967c887255a78a4588a96f9dca367c6",
"merchant_id": "c513667a-36c5-4c2a-bbba-e72e632aa906",
"processed_by": "[email protected]",
"reference_id": "ttt",
"transaction_id": "withdrawal_dev_EXPjbvx2idcBO83W5d8dTtHg3jASULPN",
"transaction_date": "2025-06-21T12:42:34Z",
"transaction_type": "withdrawal"
}
Field | Type | Description |
---|---|---|
amount | number | The transaction amount in the smallest currency unit |
remark | string | Additional note or comment about the transaction |
status | string | Transaction status (approved, rejected, pending) |
signature | string | HMAC-SHA256 signature for verifying the webhook |
merchant_id | string | Your merchant ID |
processed_by | string | Email of the admin who processed the transaction |
reference_id | string | Your reference ID for this transaction |
transaction_id | string | FundPay's unique transaction ID |
transaction_date | string | ISO 8601 timestamp of the transaction |
transaction_type | string | Type of transaction (withdrawal) |
Follow these steps to properly implement webhook handling in your application.
Contact FundPay support to register your webhook URL. This URL should point to an endpoint on your server that will receive webhook notifications.
Create an endpoint on your server to receive webhook notifications. This endpoint should:
// Using Express.js
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
// Your FundPay secret key
const SECRET_KEY = process.env.FUNDPAY_SECRET_KEY;
// Webhook endpoint
app.post('/webhooks/fundpay', async (req, res) => {
try {
const payload = req.body;
console.log('Received webhook:', payload);
// Check transaction type
if (payload.transaction_type === 'withdrawal') {
// Verify signature for withdrawal webhooks
const receivedSignature = payload.signature;
// Create a copy of the payload without the signature for verification
const payloadForVerification = {...payload};
delete payloadForVerification.signature;
// Sort keys alphabetically
const sortedPayload = {};
Object.keys(payloadForVerification).sort().forEach(key => {
sortedPayload[key] = payloadForVerification[key];
});
// Create string to sign
const stringToSign = Object.entries(sortedPayload)
.map(([key, value]) => `${key}=${value}`)
.join('&');
// Generate signature
const calculatedSignature = crypto
.createHmac('sha256', SECRET_KEY)
.update(stringToSign)
.digest('hex');
// Verify signature
if (calculatedSignature !== receivedSignature) {
console.error('Invalid signature');
return res.status(400).send('Invalid signature');
}
}
// Process the webhook based on transaction type and status
if (payload.transaction_type === 'deposit') {
await processDepositWebhook(payload);
} else if (payload.transaction_type === 'withdrawal') {
await processWithdrawalWebhook(payload);
}
// Acknowledge receipt of webhook
res.status(200).send('Webhook received');
} catch (error) {
console.error('Error processing webhook:', error);
res.status(500).send('Error processing webhook');
}
});
async function processDepositWebhook(payload) {
// Update your database based on deposit status
const { reference_id, status, amount, transaction_id } = payload;
// Example: Update order status in your database
// await db.orders.updateStatus(reference_id, status);
console.log(`Updated deposit status for ${reference_id} to ${status}`);
}
async function processWithdrawalWebhook(payload) {
// Update your database based on withdrawal status
const { reference_id, status, amount, transaction_id } = payload;
// Example: Update withdrawal request in your database
// await db.withdrawals.updateStatus(reference_id, status);
console.log(`Updated withdrawal status for ${reference_id} to ${status}`);
}
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});
<?php
// Get the webhook payload
$payload = json_decode(file_get_contents('php://input'), true);
$secret_key = getenv('FUNDPAY_SECRET_KEY');
// Log the webhook
file_put_contents('webhook_log.txt', date('Y-m-d H:i:s') . ' - ' . json_encode($payload) . "\n", FILE_APPEND);
// Check transaction type
if ($payload['transaction_type'] === 'withdrawal') {
// Verify signature for withdrawal webhooks
$received_signature = $payload['signature'];
// Create a copy of the payload without the signature for verification
$payload_for_verification = $payload;
unset($payload_for_verification['signature']);
// Sort keys alphabetically
ksort($payload_for_verification);
// Create string to sign
$string_to_sign = '';
foreach ($payload_for_verification as $key => $value) {
$string_to_sign .= $key . '=' . $value . '&';
}
$string_to_sign = rtrim($string_to_sign, '&');
// Generate signature
$calculated_signature = hash_hmac('sha256', $string_to_sign, $secret_key);
// Verify signature
if ($calculated_signature !== $received_signature) {
http_response_code(400);
echo 'Invalid signature';
exit;
}
}
// Process the webhook based on transaction type and status
if ($payload['transaction_type'] === 'deposit') {
process_deposit_webhook($payload);
} else if ($payload['transaction_type'] === 'withdrawal') {
process_withdrawal_webhook($payload);
}
// Acknowledge receipt of webhook
http_response_code(200);
echo 'Webhook received';
function process_deposit_webhook($payload) {
// Update your database based on deposit status
$reference_id = $payload['reference_id'];
$status = $payload['status'];
$amount = $payload['amount'];
$transaction_id = $payload['transaction_id'];
// Example: Update order status in your database
// $db->query("UPDATE orders SET status = ? WHERE reference_id = ?", [$status, $reference_id]);
error_log("Updated deposit status for {$reference_id} to {$status}");
}
function process_withdrawal_webhook($payload) {
// Update your database based on withdrawal status
$reference_id = $payload['reference_id'];
$status = $payload['status'];
$amount = $payload['amount'];
$transaction_id = $payload['transaction_id'];
// Example: Update withdrawal request in your database
// $db->query("UPDATE withdrawals SET status = ? WHERE reference_id = ?", [$status, $reference_id]);
error_log("Updated withdrawal status for {$reference_id} to {$status}");
}
?>