API Mobile Topup Async
Endpoint: /api/v2/service/topup/charging-async
Method: POST
Header: How to generate JWT_TOKEN
Language: en | vi
{
"X-APPOTAPAY-AUTH": Bearer JWT_TOKEN,
"Content-Type": "application/json",
"Language": LANGUAGE
}
- The partner must register an IPN URL with AppotaPay before using this API. Without an IPN URL, AppotaPay cannot deliver the transaction result to the partner.
- This API only confirms that the transaction has been accepted, it does not return the topup result. The final result is delivered through the IPN.
- See also the asynchronous flow overview.
Params
| Params | Required | Type | Description | Note |
|---|---|---|---|---|
| partnerRefId | √ | String | This is unique code corresponding to a request send | max_length: 50 |
| telco | String | Vendor name (see more Table of content list vendor) | ||
| telcoServiceType | √ | String | Service type prepaid: Topup mobile prepaid postpaid: Topup mobile postpaid | |
| productCode | √ | String | Product code (see more API to get list product code) | |
| phoneNumber | √ | String | Phone number charge topup (format: 09x, 08x,..) | |
| signature | √ | String | Signature of parameter passed to the API, the parameter is included into signature like this: partnerRefId + phoneNumber + productCode + telco + telcoServiceType (see more how to generate signature) |
Note:
- APPOTAPAY has supported Partners in proactively checking the network of the transmitted phone number.
- Partners can use the common product code table to transmit to the API without having to clearly define the product_code according to each network of the phone number (This code table is used for regular topup, not for data topup).
Data result
| Params | Type | Description |
|---|---|---|
| errorCode | Integer | Error code result.
Other codes: the transaction was not accepted (see the error code table) |
| message | String | Description error code detail |
Note: errorCode = 35 is not the final result of the transaction. The partner may only mark the order as succeeded/failed when receiving an IPN with a final status or when the transaction status API returns a final status.
Example
Request
{
"partnerRefId": "AB123",
"telco": "viettel",
"telcoServiceType": "prepaid",
"phoneNumber": "0866123456",
"productCode": "viettel_10",
"signature": "5a2774918a29cf4d2bdb78cccceb956f4c27837fad09a03a56e1df68b1bf29dd"
}
Response
Transaction accepted
{
"errorCode": 35,
"message": "Transaction is pending, please try again later"
}
Transaction not accepted
{
"errorCode": 31,
"message": "Duplicate transaction"
}
IPN (Instant Payment Notification)
When a transaction reaches its final result, AppotaPay sends a notification to the IPN URL registered by the partner. The partner verifies the data integrity through the signature parameter and then updates the order status.
Method: POST
Header
{
"Content-Type": "application/json"
}
Params sent by AppotaPay
| Params | Type | Description |
|---|---|---|
| errorCode | Integer | Transaction error code. 0 when the topup succeeded, other than 0 when it failed (see the error code table) |
| status | String | Final transaction status: success or error |
| appotapayTransId | String | Transaction id from AppotaPay |
| partnerRefId | String | Transaction id from the partner |
| phoneNumber | String | Phone number to charge topup |
| productCode | String | Product code |
| amount | Integer | Transaction amount |
| topupAmount | Integer | Amount topped up for the subscriber |
| telco | String | Vendor name Telco may be different from the input request due to actual data recorded from provider |
| telcoServiceType | String | Service type (This value may vary based on the actual information returned by the telecom provider.) |
| time | String | Topup time (in RFC-3339 standard format) |
| signature | String | Signature of the IPN data, the signed fields are (amount + appotapayTransId + errorCode + partnerRefId + phoneNumber + productCode + status + telco + telcoServiceType + time + topupAmount) |
How to verify the signature
The params used to build the signature are sorted alphabetically, joined as key=value pairs separated by &, then signed with HMAC_SHA256 using the partner SECRET_KEY.
signature = HMAC_SHA256("amount=10000&appotapayTransId=01J7G2DYZTPCGHM3AAF8ANZC7J&errorCode=0&partnerRefId=AB123&phoneNumber=0866123456&productCode=viettel_10&status=success&telco=viettel&telcoServiceType=prepaid&time=2026-08-17T10:15:30+07:00&topupAmount=10000", YOUR_SECRET_KEY)
The partner regenerates the signature from the received data and compares it with the signature sent by AppotaPay. If they do not match, the order must not be updated.
IPN examples
Successful transaction
{
"errorCode": 0,
"status": "success",
"appotapayTransId": "01J7G2DYZTPCGHM3AAF8ANZC7J",
"partnerRefId": "AB123",
"phoneNumber": "0866123456",
"productCode": "viettel_10",
"amount": 10000,
"topupAmount": 10000,
"telco": "viettel",
"telcoServiceType": "prepaid",
"time": "2026-08-17T10:15:30+07:00",
"signature": "44b2e44eaaecfbbe5d9512c323affad5a9515a7f606afc486de80b7c2f771f2f"
}
Failed transaction
{
"errorCode": 33,
"status": "error",
"appotapayTransId": "01J7G2DYZTPCGHM3AAF8ANZC7J",
"partnerRefId": "AB124",
"phoneNumber": "0866123456",
"productCode": "viettel_10",
"amount": 10000,
"topupAmount": 10000,
"telco": "viettel",
"telcoServiceType": "prepaid",
"time": "2026-08-17T10:15:30+07:00",
"signature": "046ae200d3210f0a5417ac2b3a8ccc83edf6452f00c8655fd31a13e73e1f84d8"
}
Response expected from the partner
The partner returns HTTP 200 to confirm the IPN was received successfully. Any HTTP code other than 200 is treated by AppotaPay as a failed IPN delivery.
| Criteria | Value |
|---|---|
| Success acknowledgement code | HTTP 200 |
| Connect timeout | 5 seconds |
| Response timeout | 10 seconds |
| Maximum delivery attempts | 4 (including the first attempt) |
| Interval between retries | 5 minutes |
- AppotaPay only sends an IPN once the transaction has a final result (
statusissuccessorerror), never while the transaction is still being processed. - The same transaction may be delivered several times. The partner system must prevent duplicate processing by
partnerRefId: if the order result was already updated, skip it and still returnHTTP 200. - If all 4 attempts fail, AppotaPay stops retrying. The partner should call the transaction status API for reconciliation, or contact AppotaPay to resend the IPN manually.
- Only confirm a transaction as successful when
status = success,errorCode = 0and thesignatureis valid.