PromptPay

PromptPay is Thailand’s national real‑time payment scheme, enabling 70 Million users to make one off instant bank transfers by scanning a QR code. Ezypay now supports PromptPay as a payment method that Gym operators can offer to their members.

One of the biggest advantages of PromptPay is its universal compatibility. Customers do not need to download any additional app or install new software. Any Thai banking app or e‑wallet that supports PromptPay can scan the QR code and complete the payment instantly. This includes all major Thai banks, making the experience seamless for virtually every customer in Thailand.

Using the QR string returned by Ezypay, partners can render the dynamic PromptPay QR code on customer-facing displays for walk-in payments or embed it into online checkout flows. In both cases, members can scan the QR code using their preferred banking app to complete the payment.

Customer choose to use PromptPay

A QR code specific to the payment amount is presented

Customer scans and authorise the payment

What You Need to Know

  1. Customer-initiated – No payment attempt occurs until the customer scans the QR code and authorises the payment in their banking app. Thus, it cannot be used on Ezypay's subscriptions.
  2. Single-use QR Code - Each QR code is valid for one payment only. The QR is automatically invalidated once payment is completed.
  3. Pre-set amount - The QR code presented is fixed to the invoice amount automatically without the need for customer to key in the amount for a generic QR.
  4. Time-bounded – QR codes have an expiry period. By default, PromptPay QR payments expire after 7 days if the customer takes no action and the invoice will fail.
  5. QR Code Generation Required - Ezypay provides a QR string, not a QR image. Your system is responsible for converting the returned string into a scannable QR code.
  6. No Refunds and Chargeback - Refunds are not supported for PromptPay transactions because the customer approves and authorises the transfer in advance.
  7. Customer Statement Descriptor - All PromptPay transactions will appear on the customer’s bank statement as "Xendit Tech Co. Ltd". This label cannot be changed.
  8. Asynchronous Processing – The partner platform must wait for customer action, and the payment may be cancelled or expire before that occurs without any movement of funds. The final payment outcome is identifiable via Ezypay webhook events.
  9. No Customer Notification - No customer notification is available for PromptPay, including failed payment notification.
  10. Cannot be Primary - PromptPay cannot be set as the primary payment method of a customer.

Get Started

Before processing an invoice with PromptPay, you must first create a PromptPay payment method token using the Ezypay API. Once the token is created, attach it during invoice creation. After the invoice is submitted, rely on webhook events to retrieve the QR string for customer payment and to receive the final payment outcome.

1. Adding a PromptPay Payment Method Token

  1. Create PromptPay Payment Method Token for the customer using API. This only needs to be done once per customer. The token is automatically linked with the specified customerId and can be reused for all future invoices for that customer.
  2. Store the paymentMethodToken returned securely and reuse this token when creating PromptPay invoices for the same customer.
{
   "accountHolderName": "John Doe",
   "countryCode": "TH",
   "qrType": "PROMPTPAY",
   "termAndConditionAgreed": true,
   "customerId": "f97d05cd-bd98-418d-8f78-a59d04ca47ee"
}
{
  "type": "QRPAYMENT",
  "qrPayment": {
    "merchantId": "5b390bbe-6c65-4d06-96d4-0d37b39eed24",
    "accountHolderName": "John Doe",
    "qrType": "PROMPTPAY",
    "countryCode": "TH",
    "customerId": "f97d05cd-bd98-418d-8f78-a59d04ca47ee"
  },
  "paymentMethodToken": "e8eaef9d-eac2-46ac-80c2-71a69e2ae6b8"
}

2. Create a PromptPay Invoice

Use Create Invoice API to create invoice that will produce a PromptPay QR string.

  1. Create Invoice using the PromptPay Token
    1. Provide the customerId, paymentMethodToken and invoice item in the items array.
    2. The invoice response from Ezypay will include a qrData object.
    3. Initially, qrData.qrString will appear as GENERATING. Wait for the qr_response webhook from Ezypay to receive the actual QR string.
📘

Use externalInvoiceId as the idempotency key for each invoice.

{
    "customerId": "f97d05cd-bd98-418d-8f78-a59d04ca47ee",
  	"paymentMethodToken": "6f2afde5-7c6d-4107-ab59-1f0eafe10734",
		"externalInvoiceId": "PARTNER-INV-0001",
    "items": [
        {
            "amount": {
                "currency": "THB",
                "value": 10000
            },
            "tax": {
                "rate": 7
            },
            "description": "GYM Membership 12 month Payment"
        }
    ]
}
{
    "id": "2326be94-1726-4fff-b1b0-50677640f5ec",
    "creditNoteId": null,
    "documentNumber": "IN0000000000000216",
    "date": "2025-10-24",
    "dueDate": "2025-10-24",
  	"scheduledPaymentDate": null,
		"externalInvoiceId": "PARTNER-INV-0001",
    "status": "PROCESSING",
    "memo": null,
    "items": [
        {
            "description": "GYM Membership 12 month Payment",
            "amount": {
                "currency": "THB",
                "value": 10000.00,
                "type": null
            },
            "tax": {
                "rate": 7.00
            },
            "id": "99259951-0169-43ca-b202-315bde56207e",
            "type": "on_demand_payment",
            "discounted": {
                "currency": "THB",
                "value": 0.00,
                "type": null
            },
            "accountingCode": null,
            "reference": null
        }
    ],
    "amount": {
        "currency": "THB",
        "value": 10000.00,
        "type": null
    },
    "amountWithoutDiscount": {
        "currency": "THB",
        "value": 10000.00,
        "type": null
    },
    "totalDiscounted": {
        "currency": "THB",
        "value": 0.00,
        "type": null
    },
    "totalRefunded": {
        "currency": "THB",
        "value": 0.00,
        "type": null
    },
    "totalTax": {
        "currency": "THB",
        "value": 9.09,
        "type": null
    },
    "customerId": "f97d05cd-bd98-418d-8f78-a59d04ca47ee",
    "subscriptionId": null,
    "checkoutId": null,
    "subscriptionName": null,
    "paymentMethodToken": "6f2afde5-7c6d-4107-ab59-1f0eafe10734",
    "paymentMethodData": {
        "paymentMethodToken": "6f2afde5-7c6d-4107-ab59-1f0eafe10734",
        "customerId": "f97d05cd-bd98-418d-8f78-a59d04ca47ee",
        "type": "QRPAYMENT",
        "bank": null,
        "payTo": null,
        "card": null,
        "invalidReason": null,
        "lastUsedOn": "2025-10-23T09:05:14.955",
        "replacedPaymentMethodData": null,
        "wallet": null,
        "qrPayment": {
            "qrType": "PROMPTPAY",
            "accountHolderName": "John Doe",
            "countryCode": "TH"
        },
        "valid": true,
        "primary": false
    },
    "autoPayment": true,
    "processingModel": null,
    "transactionSource": null,
    "createdOn": "2025-10-24T00:29:46.979",
    "payNowUrl": null,
    "channel": "api",
    "checkoutResult": null,
    "customerFirstName": null,
    "customerLastName": null,
    "qrData": {
      "qrString": "GENERATING",
      "expires": "2025-10-31T00:29:46.979"
    },
    "manualRetryPossible": true,
    "paymentMethodInvalid": false
}
  1. Next, retrieve and store the id of the created invoice for tracking the webhook.
🚧

Do not generate a QR code yet—wait for the qr_response webhook.

3. Handle qr_response Webhook

Once the QR string is available, Ezypay will send a qr_response webhook. Make sure you have subscribed to it.

📘

The QR string will be available from Ezypay in near real-time.

  1. Retrieve the data.qrData.qrString and use it to generate a QR code image, which is presented to the customer.
{
  "requestId": "ae2e8868-2ff2-48db-b4f8-1bb5eae09c37",
  "merchantId": "ef9d4c48-5176-4fa5-9f64-f5fd5e81a55e",
  "eventType": "QR_RESPONSE",
  "createdOn": "2025-10-05T12:45:04.786",
  "data": {
    "invoiceId": "0d5fdd7c-a2aa-4e70-a1b9-bc5f1202e20f",
    "invoiceStatus": "PENDING_QR_PAYMENT",
    "type": "QRPAYMENT",
    "qrType": "PROMPTPAY",
    "qrData": {
      "qrString": 		"00020101021230830016A00000067701011201150105560068127480218800000250815112795031800064VR8QCUD6EB2RD530376454045.005802TH5910GBPrimePay6304B160",
      "expires": "2025-10-12T12:45:04.786"
    },
    "amount": 10000,
    "currency": "THB"
  }
}
  1. At this point, the customer is expected to use their mobile phone to scan the QR code and authorise the payment within their banking app.
📘

Show the expiry of the QR code.

  1. Set up a webhook to receive outcome for invoice generated.
    1. invoice_paid - Payment success.
    2. invoice_past_due Payment failed
  2. Display the payment outcome and ensure you capture the fees charge by Ezypay to customer is also displayed. Use the items.type field to identify different fee type from Ezypay. Please refer to Ezypay's fees for more information about Ezypay's fees.

Cancelling a QR payment

A QR payment can be cancelled before the customer scans the QR code. For the full API's spec, refer to Cancel a QR Payment API

Common Scenarios

  • Customer walks away from the counter
  • Customer decides not to proceed with payment
  • Merchant wants to void the transaction before payment

How to cancel the QR Payment

  1. Call the Cancel QR Payment API.
  2. Wait for the qr_response webhook.
  3. In the webhook:
    1. If success - data.qrData.qrString will be CANCELED and data.cancelStatus is SUCCESS.
    2. If failed- data.qrData.qrString will be remains and data.cancelStatus is FAILED.
  4. The invoice status will update to: UNPAID
{
  "requestId": "ae2e8868-2ff2-48db-b4f8-1bb5eae09c37",
  "merchantId": "ef9d4c48-5176-4fa5-9f64-f5fd5e81a55e",
  "eventType": "QR_RESPONSE",
  "createdOn": "2025-10-05T12:45:04.786",
  "data": {
    "invoiceId": "0d5fdd7c-a2aa-4e70-a1b9-bc5f1202e20f",
    "invoiceStatus": "UNPAID",
    "type": "QRPAYMENT",
    "qrType": "PROMPTPAY",
    "qrData": {
      "qrString": "CANCELED",
      "expires": ""
    },
    "amount": 100,
    "currency": "THB",
    "cancelStatus": "SUCCESS"
  }
}

Expired QR payment

If no customer action is taken within 7 days, the QR payment will automatically expire.

Expiry Behaviour

  1. QR code becomes invalid.
  2. Invoice status updates to: PAST_DUE
  3. An invoice_past_due webhook is triggered.
  4. The failure reason will be: EXPIRED