API documentation

Accept FPX, card and e-wallet payments from your own website or system. This guide covers sending a customer to the payment page, receiving the result, querying an order, and the cart API.

1. Overview

There are two ways to integrate. Pick one; both report the result in the same way.

Payment pageCart API
Best forYou already calculate the order total yourselfYou want PaySeller to hold the items, postage and tax
HowYour page posts a form to /api/v1/payYour server calls /api/v1/cart (JSON)
API keyTravels through the customer's browserStays on your server

The flow is the same in both cases: (1) the customer reaches the PaySeller payment page and pays; (2) PaySeller posts the result to your Callback URL; (3) the customer is sent back to your Redirect URL; (4) your server confirms the result with the query API before fulfilling the order.

  • Base URL: https://payseller.my. HTTPS only.
  • Currency is MYR. Minimum amount per payment is RM 2.00.
  • There is no sandbox. Test with a real payment of a small amount.

2. Credentials and settings

Log in to your PaySeller account and open the Websites page. The Merchant ID and API Key appear once your account is verified. Each website has:

SettingUse
Merchant IDSent as merchant_id in every request
API KeySent as api_key. Treat it as a password
Callback URLYour server endpoint that receives the payment result
Redirect URLThe page the customer returns to after paying
Each website has a transaction limit. A payment above the limit is rejected with Over transaction limit. Contact PaySeller support to change it.

3. Payment page

Send the customer's browser to https://payseller.my/api/v1/pay with the fields below, as a form POST (recommended) or a GET link. PaySeller shows the payment page and takes the customer to the bank, card or e-wallet.

FieldRequiredDescription
api_keyYesYour API Key
merchant_idYesYour Merchant ID
order_noYesYour own order number. Used in the notification and the query API
order_totalYesAmount in MYR, e.g. 198.00. Minimum 2.00
payment_codeNocc card, fpx online banking, ewallet e-wallet. Default is card
customer_nameNoShown on the payment page and in receipts
customer_emailNoThe customer's email for the receipt
customer_countryNoDefault Malaysia
HTML
<form method="post" action="https://payseller.my/api/v1/pay">
  <input type="hidden" name="api_key"        value="YOUR_API_KEY">
  <input type="hidden" name="merchant_id"    value="YOUR_MERCHANT_ID">
  <input type="hidden" name="order_no"       value="INV018250">
  <input type="hidden" name="order_total"    value="198.00">
  <input type="hidden" name="customer_name"  value="Tan Mei Ling">
  <input type="hidden" name="customer_email" value="meiling@example.com">
  <input type="hidden" name="payment_code"   value="fpx">
  <button type="submit">Pay with PaySeller</button>
</form>

If the request is rejected, the response is HTTP 200 with a plain-text message instead of the payment page:

MessageCause
API Key Errorapi_key and merchant_id do not match an active website
Account not activated. Please contact PaySeller customer supportapi_key is missing or empty
Minimum transaction RM 2order_total is below 2.00 or not a number
Over transaction limitorder_total is above the website's transaction limit
  • Every load of the payment page creates a new transaction. If a customer tries three times, the query API returns three transactions for the same order_no.
  • Because the form passes through the customer's browser, the amount can be altered. Always compare order_total in the result with your own order before fulfilling it.

4. Payment notification

When a payment finishes, PaySeller sends an HTTP POST (application/x-www-form-urlencoded) from its server to your Callback URL. It always contains these 10 fields:

FieldDescription
ord_dateDate of the payment. The format depends on the payment channel, so treat it as text
order_noYour order number
order_totalAmount paid
payment_typeCredit Card, Bank, TouchNGo, Boost, GrabPay, MayBank QRPay, ShopeePay…
source_typeCard type, when available
bankBank name for FPX
bank_referenceBank or wallet reference number
raw_error_descriptionResult text from the bank or gateway
wcIDGateway transaction ID
returncodeResult code, see below
Example fields
ord_date=07 November 2026
order_no=INV018250
order_total=198.00
payment_type=Bank
source_type=
bank=Hong Leong Bank Berhad
bank_reference=21739274F0AD
raw_error_description=Approved
wcID=gmhljksc
returncode=100
returncodeMeaning
100Paid
E1Failed
E2Cancelled by the customer
Transaction Incomplete (Pending)Not final yet. Query again later
Anything elseNot paid. The original gateway status
  • Reply with HTTP 200 within 10 seconds. The content of your reply is ignored.
  • The notification is sent for failed and cancelled payments too, not only successful ones.
  • The same result can arrive more than once. Mark an order as paid only once.
  • A notification that cannot be delivered is not sent again. If your server was down, use the query API to catch up.
  • Some values may contain %20 in place of spaces. Decode ord_date and raw_error_description before showing them.
The notification is not signed. Anyone who knows your Callback URL could post to it. Before you mark an order as paid, call the query API from your server and check that a transaction with returncode 100 and the right amount exists.
PHP
<?php
// callback.php — receives the payment notification from PaySeller
$orderNo = $_POST['order_no'] ?? '';

// 1. Never trust the notification alone: confirm with the query API.
$ch = curl_init('https://payseller.my/api/v1/query');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'api_key'     => 'YOUR_API_KEY',
        'merchant_id' => 'YOUR_MERCHANT_ID',
        'order_no'    => $orderNo,
        'format'      => 'json',
    ]),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);

// 2. Paid = any transaction of this order with returncode "100".
$paid = false;
foreach ($data['transactions'] ?? [] as $t) {
    if ($t['returncode'] === '100' && (float)$t['order_total'] === expectedTotal($orderNo)) {
        $paid = true;
    }
}

// 3. Mark the order paid only once (notifications can arrive more than once).
if ($paid) markOrderPaidOnce($orderNo);

http_response_code(200);
echo 'OK';

After the notification, the customer's browser is sent to your Redirect URL with a form POST carrying the same 10 fields. Use it only to show a thank-you or a try-again page. Do not fulfil the order from the redirect: the customer may close the browser before it happens, and the fields pass through the browser.

5. Query an order

Call https://payseller.my/api/v1/query from your server, by POST (form fields) or GET, to get the payment result of an order.

FieldRequiredDescription
api_keyYesYour API Key
merchant_idYesYour Merchant ID
order_noYesThe order number you sent when paying
formatYesjson. Without it the response is an HTML table
curl
curl -X POST https://payseller.my/api/v1/query \
  -d api_key=YOUR_API_KEY \
  -d merchant_id=YOUR_MERCHANT_ID \
  -d order_no=INV018250 \
  -d format=json
Response
{
  "transactions": [
    {
      "ord_date": "07%20November%202026",
      "order_no": "INV018250",
      "order_total": "198.00",
      "payment_type": "bank",
      "source_type": "",
      "bank": "Hong Leong Bank Berhad",
      "bank_reference": "21739274F0AD",
      "raw_error_description": "Approved",
      "wcID": "gmhljksc",
      "returncode": "100",
      "ord_mercref": "836014_113739"
    }
  ]
}
  • One entry per payment attempt, newest first. An order that was never sent to the payment page returns {"transactions":[]}.
  • The order is paid when any entry has returncode equal to 100.
  • ord_mercref is the PaySeller transaction reference. Quote it when you contact support.
  • payment_type here is a short code (cc, bank, tng, boost, grabpay, shopeepay…), while the notification sends a title (Credit Card, Bank…).
  • Wrong credentials return HTTP 200 with the plain text API Key Error, not JSON. Check that the response starts with { before parsing.
  • Use a 30 second timeout. PaySeller asks the gateway for the latest status of unfinished payments, which can take a few seconds.
  • Querying an unfinished payment may trigger another notification to your Callback URL with the latest result.
Node.js
// Node.js 18+
const res = await fetch("https://payseller.my/api/v1/query", {
  method: "POST",
  body: new URLSearchParams({
    api_key: process.env.PAYSELLER_API_KEY,
    merchant_id: process.env.PAYSELLER_MERCHANT_ID,
    order_no: "INV018250",
    format: "json",
  }),
  signal: AbortSignal.timeout(30_000),
});
const text = await res.text();
if (!text.startsWith("{")) throw new Error(text); // e.g. "API Key Error"
const paid = JSON.parse(text).transactions.some((t) => t.returncode === "100");

6. Cart API

With the cart API your server creates a cart, and PaySeller calculates postage and tax, takes the customer's delivery details and collects the payment. Your API Key never reaches the browser.

  • Base URL: https://payseller.my/api/v1/cart. Send and receive JSON (Content-Type: application/json).
  • Authenticate with the headers X-Merchant-Id and X-Api-Key, or with merchant_id and api_key in the JSON body.
  • Call it from your server only. Limit: 120 requests per minute per merchant.

Create a cart with POST /api/v1/cart:

curl
curl -X POST https://payseller.my/api/v1/cart \
  -H "Content-Type: application/json" \
  -H "X-Merchant-Id: YOUR_MERCHANT_ID" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{
    "order_no": "INV018250",
    "items": [
      { "item_id": 1, "quantity": 2, "option_1": "M", "option_2": "Navy" },
      { "title": "Custom engraving", "item_code": "ENG", "price": 15.00, "weight": 0, "quantity": 1 }
    ],
    "customer": {
      "name": "Tan Mei Ling", "email": "meiling@example.com", "phone": "0123456789",
      "address_line_1": "53, Jalan Mawar 2", "postcode": "81200",
      "city": "Johor Bahru", "state": "Johor", "shipping_area": "WM"
    },
    "redirect_url": "https://example.com/thank-you",
    "callback_url": "https://example.com/payseller/callback"
  }'
FieldRequiredDescription
itemsYes1 to 50 lines. Use item_id for a product in your PaySeller catalogue (PaySeller's price applies), or title and price for a custom line. quantity is 1 to 99
order_noNoYour order number. If omitted PaySeller generates one. A number that is already paid cannot be reused
customerNoPre-fills the checkout page. shipping_area is WM (West Malaysia), EM (East Malaysia) or SG (Singapore)
redirect_urlNoOverrides the website's Redirect URL for this cart
callback_urlNoOverrides the website's Callback URL for this cart
Response
{
  "status": "ok",
  "cart_id": "c_OrqeDZ2cmRy-LdB87JZdbQ",
  "cart_status": "open",
  "checkout_url": "https://payseller.biz.my/123/checkout/c_OrqeDZ2cmRy-LdB87JZdbQ",
  "expires_at": "2026-11-18T07:20:00.000Z",
  "order_no": "INV018250",
  "items": [
    { "line_id": 1, "item_id": 1, "item_code": "A001", "title": "Cotton Tee",
      "option_1": "M", "option_2": "Navy", "price": "89.00", "weight": 0.3,
      "quantity": 2, "amount": "178.00", "custom": false },
    { "line_id": 2, "item_code": "ENG", "title": "Custom engraving",
      "price": "15.00", "weight": 0, "quantity": 1, "amount": "15.00", "custom": true }
  ],
  "subtotal": "193.00"
}

Send the customer to checkout_url. They confirm the delivery details, choose a payment method and pay. The cart is valid for 7 days. When shipping_area is known, the response also includes postage, tax and total.

MethodPathUse
GET/api/v1/cart/{cart_id}?shipping_area=WMRead the cart and its totals
POST/api/v1/cart/{cart_id}/itemsAdd lines (same format as items)
PATCH/api/v1/cart/{cart_id}/items/{line_id}Change quantity: {"quantity": 3}. 0 removes the line
DELETE/api/v1/cart/{cart_id}/items/{line_id}Remove a line
POST/api/v1/cart/{cart_id}/checkoutCheck out from your server and get a payment link
DELETE/api/v1/cart/{cart_id}Cancel the cart

If you collect the delivery details yourself, skip the PaySeller checkout page: call checkout with customer, shipping_area and payment_method (fpx, cc or ewallet), then send the customer to pay_url. The link is valid for 24 hours.

curl
curl -X POST https://payseller.my/api/v1/cart/c_OrqeDZ2cmRy-LdB87JZdbQ/checkout \
  -H "Content-Type: application/json" \
  -H "X-Merchant-Id: YOUR_MERCHANT_ID" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{ "payment_method": "fpx", "shipping_area": "WM" }'

# Response
{ "status": "ok", "cart_id": "c_OrqeDZ2cmRy-LdB87JZdbQ",
  "order_no": "INV018250", "total": "201.00",
  "pay_url": "https://payseller.my/api/v1/pay.php?pay_token=..." }

The result is reported exactly as in sections 4 and 5: the same 10-field notification, and the same query API with your order_no.

Errors return an HTTP 4xx status and a JSON body:

Error
HTTP/1.1 409
{ "status": "error", "code": "OUT_OF_STOCK", "message": "Cotton Tee (M / Navy): only 1 left" }
codeMeaning
AUTH_FAILEDWrong Merchant ID or API Key
INVALID_REQUESTA field is missing or malformed. message says which
ITEM_NOT_FOUNDThe product does not exist or is not on sale
INVALID_OPTIONThe option is not one of the product's options
INVALID_QUANTITYQuantity is not 1 to 99
INVALID_ITEMA custom line has no title or an invalid price
TOO_MANY_ITEMSMore than 50 lines
CART_NOT_FOUNDNo such cart
CART_EXPIREDThe cart is older than 7 days (HTTP 410)
CART_CLOSEDThe cart is already checked out or cancelled (HTTP 409)
LINE_NOT_FOUNDNo such line_id
ORDER_NO_USEDThis order number is already paid
AMOUNT_TOO_LOWThe total is below RM 2.00
OUT_OF_STOCKNot enough stock at checkout (HTTP 409). message lists the items
RATE_LIMITEDToo many requests (HTTP 429)

7. Before you go live

  • The Callback URL and Redirect URL are saved on the Websites page and reachable over HTTPS from the internet.
  • Your callback handler confirms every result with the query API before marking an order as paid.
  • It compares the paid amount with your own order total.
  • It marks an order as paid only once, even when the result arrives several times.
  • Orders still unpaid after a while are re-checked with the query API, in case a notification was missed.
  • The API Key is stored on your server, not in an app or in JavaScript.
  • You have completed one real payment for each payment method you offer, and one cancelled payment.

Need help? Contact PaySeller support with your Merchant ID, the order_no and, when you have it, the ord_mercref.