Signature generator
Sign requests with HMAC-SHA256, and verify the callbacks FundPay sends back. Try it live below, or copy a ready-made snippet in your language.
How it works
Five steps — the live tool and every snippet follow this exactly.
- 1Collect all request parameters (excluding signature).
- 2Sort the parameter keys alphabetically (A–Z).
- 3Form-url-encode into a key1=value1&key2=value2 string (space → +).
- 4Compute HMAC-SHA256 of that string with your merchant secret key.
- 5Send the lowercase-hex result as the signature field.
Live generator
Paste your params and secret key to compute a signature instantly.
Works for deposit and withdrawal — empty fields are excluded, and a nested source_bank_account is re-keyed to source_account_* (account_number → source_account_no).
Computed live in your browser with the Web Crypto API — the secret never leaves this page.
———Code examples
Full API reference# The sorted, form-url-encoded string of the fields you're signing:
STRING='amount=1000&backend_return_url=https%3A%2F%2Fexample.com%2Fwebhook¤cy=THB&frontend_return_url=https%3A%2F%2Fexample.com%2Freturn&merchant_id=d0bf1184-c1c9-4101-b034-97e3b44edf4e&payment_method=bank_transfer&reference=ORD1234567&source_account_bank_code=kbank&source_account_name=TEST+HELLO&source_account_no=1234567×tamp=2025-07-29T21%3A23%3A29%2B07%3A00'
# HMAC-SHA256 with your merchant secret key -> lowercase hex
SIGNATURE=$(printf '%s' "$STRING" | openssl dgst -sha256 -hmac "$MERCHANT_SECRET_KEY" | sed 's/^.* //')
echo "$SIGNATURE"Callback verification
FundPay POSTs to your backend_return_url when a transaction settles. Verify the signature before trusting it. Deposits and withdrawals share one shape and one rule — transaction_type tells them apart, and status is approved, rejected, expired or pending.
Fields included in the signature
Exactly seven, sorted alphabetically — for deposits and withdrawals alike.
- amount
- merchant_id
- reference_id
- status
- transaction_date
- transaction_id
- transaction_type
- Sign the amount exactly as it arrives. 1500 signs as 1500, not 1500.00. Do not reformat or round it.
- Percent-encode every value. transaction_date always contains colons, and reference_id may too — a reference like 10000001:20000002:30000003 must encode to 10000001%3A20000002%3A30000003.
- Callbacks carry no timestamp. De-duplicate on transaction_id instead — delivery is at-least-once, and FundPay retries any non-2xx response.
Callback body
one tab per transaction_type// approved
{
"amount": 1234.56,
"status": "approved",
"signature": "0b661b18e4d54bb318a6548f87c438e7851c88dbe8badb5fe5ace4d5422e5f26",
"merchant_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reference_id": "WD9322299",
"transaction_id": "withdrawal_prd_EXAMPLE0000000000000000000000000",
"transaction_date": "2026-01-15T09:30:00Z",
"transaction_type": "withdrawal"
}
// rejected — carries "remark", which is NOT part of the signature
{
"amount": 1500,
"remark": "เลขบัญชีไม่ถูก",
"status": "rejected",
"signature": "89ea93e29d4d8d73ab35069174292f1b0ab9985faa0dddc4328aa79236b0dfec",
"merchant_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reference_id": "WD9322300",
"transaction_id": "withdrawal_prd_EXAMPLE1111111111111111111111111",
"transaction_date": "2026-01-16T14:05:00Z",
"transaction_type": "withdrawal"
}Verify it
one verifier handles both# Every value here is fictitious, signed with this example key so you can run it
# as-is and check your implementation against a known-good vector:
MERCHANT_SECRET_KEY=example_merchant_secret_key
verify() { # $1 = string to sign, $2 = signature from the callback body
actual=$(printf '%s' "$1" | openssl dgst -sha256 -hmac "$MERCHANT_SECRET_KEY" | sed 's/^.* //')
[ "$actual" = "$2" ] && echo "valid" || echo "INVALID"
}
# --- Withdrawal, rejected --------------------------------------------------
# The body also carries "remark": "เลขบัญชีไม่ถูก" - it is left out of the string.
verify \
'amount=1500&merchant_id=3fa85f64-5717-4562-b3fc-2c963f66afa6&reference_id=WD9322300&status=rejected&transaction_date=2026-01-16T14%3A05%3A00Z&transaction_id=withdrawal_prd_EXAMPLE1111111111111111111111111&transaction_type=withdrawal' \
'89ea93e29d4d8d73ab35069174292f1b0ab9985faa0dddc4328aa79236b0dfec'
# --- Deposit, approved -----------------------------------------------------
# Same seven fields. Note the colons in reference_id encoded as %3A.
verify \
'amount=1000&merchant_id=3fa85f64-5717-4562-b3fc-2c963f66afa6&reference_id=10000001%3A20000002%3A30000003&status=approved&transaction_date=2026-01-15T09%3A30%3A00Z&transaction_id=deposit_prd_EXAMPLE0000000000000000000000000&transaction_type=deposit' \
'55b3d2a9b728eac4399c9a051cfc0a48ad95fc458320e769de05504a54112541'