Appearance
Make Payment
Legacy Endpoint Name
The endpoint path /v2/topup is a legacy name. This single endpoint processes all transaction types: mobile reload, bill payments, vouchers, gift cards, eSIM, and more.
Process transactions across the IIMMPACT platform. This is a unified endpoint for all product offerings, including JomPAY, mobile reload, gift cards, vouchers, eSIM purchases, and more.
TIP
For a step-by-step integration walkthrough, see the Make Payment Guide.
Idempotency
The API supports idempotency — you can safely retry requests without creating duplicate transactions. Store and re-use the same refid for the same transaction.
Check Final Transaction Status: Call this API again with the identical request parameters and the original refid used during initiation. The API will return the current status of the transaction.
API Endpoint
http
POST https://api.iimmpact.com/v2/topupDANGER
Staging server statuses and failure reasons might not match production due to limited validation.
Request Headers
| Header | Description | Required |
|---|---|---|
X-Api-Key | Your API key | Yes |
X-Timestamp | Unix timestamp in seconds | Yes |
X-Nonce | Unique request identifier | Yes |
X-Signature | HMAC-SHA256 signature (v1=...) | Yes |
See API Key Authentication for how to sign your requests.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
refid | string | Yes | Your unique reference ID for this transaction. See refid constraints. |
product | string | Yes | Product code |
remarks | string | No | Optional field for any additional remarks |
account | string | Yes | Recipient's account number or identifier (e.g., bill account number, mobile number) |
amount | string | number | Yes | Accepts both string and number in the request body. Internally parsed as decimal. The response always returns a number (integer if whole number, decimal otherwise). |
extras | object | No | Additional parameters required by specific products (see below) |
extras.subproduct_code | string | No | Sub-product code |
extras.ic_number | string | No | IC number without dashes |
extras.biller_code | string | No | Biller code |
extras.ref2 | string | Conditional | Required by some JomPAY billers. The ref2 value is typically printed on the user's JomPAY bill. Use bill presentment to validate. The API returns an error if ref2 is required but not provided. |
refid Constraints
| Constraint | Value |
|---|---|
| Max length | 50 characters |
| Allowed characters | Any non-empty string |
| Uniqueness scope | Per reseller account (not global) |
| Idempotency | Permanent — re-querying with an existing refid always returns the original transaction |
Example Request Body
jsonc
{
"refid": "321479-0-30f4f209-ee",
"product": "GC",
"account": "0123456789",
"amount": 10.00, // Strings also work, e.g. "10.00"
"remarks": "",
"extras": {}
}Extras Requirements by Product
| Parameter | JomPAY | PTPTN | Mobile Data | Others |
|---|---|---|---|---|
subproduct_code | - | Mandatory | Mandatory | - |
ic_number | Mandatory (payee IC) | Mandatory (recipient IC) | - | - |
biller_code | Mandatory | - | - | - |
ref2 | Conditional | - | - | - |
AMLA Compliance — JomPAY
Due to AMLA (Anti-Money Laundering Act 2001) requirements, it is mandatory to perform eKYC (electronic Know Your Customer) on your end-users for all JomPAY transactions. Pass the verified IC number (Malaysians) or Passport number (non-Malaysians) via the ic_number field. Failure to comply, or providing fictitious/invalid numbers, may lead to account suspension.
Response 200
| Field | Type | Description |
|---|---|---|
data | object | Transaction result |
data.statusCode | number | Status code — see Payment Errors |
data.status | string | Outcome: Accepted, Processing, Succesful, or Failed |
data.account | string | Account number |
data.product | string | Product code |
data.productName | string | Product name |
data.amount | number | Amount paid |
data.sn | string | Serial number from the provider/operator |
data.pin | string | PIN for vouchers, gift cards, etc. |
data.expiry | string | Voucher expiration date (yyyymmdd) |
data.cost | number | Your wholesale cost for this transaction (deducted from your IIMMPACT wallet balance). |
data.balance | number | Current wallet balance |
data.remarks | string | Additional notes about the transaction |
data.refid | string | Your unique reference ID |
data.timestamp | string | Transaction timestamp |
data.note | string | Instructions or information for the user regarding the product |
data.voucherlink | string | Link to access or redeem the voucher |
Spelling Note
The success status value is spelled Succesful (single 's'), not Successful. This is a legacy quirk that cannot be changed. Match this exact string in your code.
Example Response:
json
{
"data": {
"statusCode": 20,
"status": "Succesful",
"account": "0123456789",
"product": "GC",
"productName": "Grab Gift Code",
"amount": 5,
"sn": "106648697",
"pin": "MPHE39G3WL",
"expiry": "20251116",
"cost": 5,
"balance": 50.54,
"remarks": "",
"refid": "321479-0-30f4f209-ee",
"timestamp": "2025-05-20 13:09:34",
"note": "Insert voucher code into Use Grab Gifts under Use Offers section upon check out",
"voucherlink": "https://api.grab.com/gifts/v2/go?id=7464957319334bb0afbd5980738a2b50"
}
}Response 400
Pre-transaction validation errors (invalid product, insufficient balance, invalid denomination, invalid account, etc.) return HTTP 400 with the same {"data": {...}} body shape as HTTP 200.
Key Difference from HTTP 200
HTTP 400 means no transaction was created and the refid is not consumed. You can safely retry with the same refid after correcting the input. This is different from an HTTP 200 Failed response, where a transaction was created.
| Field | Type | Description |
|---|---|---|
data | object | Validation error result |
data.statusCode | number | Status code — see Payment Errors |
data.status | string | Always Failed for validation errors |
data.account | string | Account number from the request |
data.product | string | Product code from the request |
data.amount | number | Amount from the request |
data.cost | number | Always 0 (no transaction was processed) |
data.balance | number | Current wallet balance (unchanged) |
data.remarks | string | Reason for the validation failure (e.g., Invalid_Denomination, Insufficient_Balance) |
data.refid | string | Your reference ID (not consumed — can be reused) |
data.timestamp | string | Timestamp of the response |
Example Response:
json
{
"data": {
"statusCode": 58,
"status": "Failed",
"account": "0123456789",
"product": "DB",
"amount": 22.23,
"cost": 0,
"balance": 50.54,
"remarks": "Invalid_Denomination",
"refid": "my-ref-id",
"timestamp": "2026-03-16 13:09:34"
}
}Transaction Status Values
The behavior of the Failed status depends on the HTTP status code:
| Stage | HTTP | Status | Transaction Created? | Retry? |
|---|---|---|---|---|
| Validation fails (invalid amount, account, denomination, insufficient balance, etc.) | 400 | Failed | No | Yes — same refid, fix input |
| Request accepted, pending processing | 200 | Accepted | Yes | Re-query same refid |
| Still processing | 200 | Processing | Yes | Re-query same refid |
| Transaction completed | 200 | Succesful | Yes | No |
| Transaction failed at provider (via re-query/callback) | 200 | Failed | Yes | No — use new refid |
| Transaction voided | 200 | Refund | Yes | No |
Distinguish HTTP 400 Failed vs HTTP 200 Failed
- HTTP 400 +
Failed= no transaction was created,refidnot consumed. Retry with the samerefidafter fixing the input. - HTTP 200 +
Failed= a transaction was created and failed at the provider level. This is final — use a newrefidfor the next attempt.
Recommended Polling Strategy
When a transaction returns Processing, poll every 5-10 seconds by re-querying with the same refid. Most transactions resolve within 30 seconds. Always combine polling with callbacks — polling provides immediate UX feedback, while callbacks are the reliable final status delivery mechanism.
