Skip to main content

Zalopay Recurring enables merchants to collect subscription or recurring payments directly from a customer's Zalopay wallet in Vietnam. Customers authorize a recurring payment agreement (or enrollment) once in the Zalopay app during the initial transaction, allowing merchants to perform future debits without requiring manual approval for every payment.

Zalopay is an e-wallet in Vietnam. Zalopay Recurring is built on Zalopay's Agreement Payment (auto-debit) solution, in which the customer binds their Zalopay wallet to the merchant and EBANX debits the wallet on each merchant request.

This capability is commonly used for SaaS and digital subscriptions, gaming memberships and in-app subscriptions, streaming and entertainment services, e-learning platforms, and utilities and telecom services.

Key features​

FeaturePurposeBenefit
Enrollment authorizationCustomers bind their Zalopay wallet to the merchant by confirming the agreement in the Zalopay app.Enables secure customer consent while eliminating the need for repeated authorization per payment.
Merchant-initiated chargesCreate new charges linked to a previously accepted and valid enrollment.Simplifies billing automation and reduces friction for subscription-based services.
Subscription lifecycle managementProvides the ability to query and cancel subscription enrollments.Gives merchants operational control over subscriptions and improves customer account management.
RefundsFull and partial refunds of confirmed payments within 180 days.Allows merchants to return funds without contacting the customer.
Payment notificationsSends asynchronous updates whenever payment or enrollment status changes.Ensures merchants receive real-time transaction updates for reconciliation and order fulfillment.

How it works​

  1. Customer enrollment - The merchant creates the enrollment and redirects the customer to confirm the agreement in the Zalopay app.
  2. Enrollment confirmation - The customer confirms the agreement in the Zalopay app and the enrollment status changes to accepted.
  3. Recurring payment request - The merchant triggers a charge linked to the accepted enrollment whenever required. No customer action is needed.
  4. Payment updates - EBANX sends payment status update notifications to the merchant.
Transaction limits
  • Minimum amount: VND 1 per transaction.
  • Maximum amount: VND 20,000,000 per transaction.
  • Maximum accumulated amount: VND 100,000,000 per month per customer (auto-debit).
  • Maximum daily amount for the Zalopay wallet balance: VND 50,000,000.
  • Zalopay accepts amounts in VND only, with no decimal places.

Apart from the limits above, customers can set their own limits inside the Zalopay wallet.

Requirements​

  • API authentication - All API requests must be authenticated using JWS (JSON Web Signature). The legacy integration_key is being deprecated — if you still use it, follow the Migration Guide to transition to JWS. If you don't have credentials yet, complete the Merchant Signup Form.
  • Familiarity with EBANX Direct - This setup follows the same general structure as other payment methods, with a few unique parameters. See EBANX Direct for more info.

Instructions​

Follow the steps below.

  1. Select your environment
    ​

    Select the appropriate environment for your integration. Use the sandbox environment for testing or the production environment for live transactions.

    Select the appropriate environment for your integration. Use the sandbox environment for testing, or the production environment for live transactions. Use the URL for your HTTP requests based on your selection.

    Check the method's availability for your country and tenant in Payment Methods by Country. Questions about tenant and environment? Check our API Operability and API Endpoints.

    Cross-border sandbox
    https://sandbox.ebanxpay.com/ws/userenrollment
  2. Define enrollment parameters
    ​

    To start the recurring payment process, create the enrollment using the /ws/userenrollment endpoint. This step collects the customer details and returns the URL where the customer confirms the agreement in the Zalopay app.

    Customers are required to log in to Zalopay and confirm the agreement. Once the agreement is confirmed, the Zalopay wallet is linked and can be charged.

    Basic parameters​

    ParameterRequirementDescription
    integration_keyRequiredYour EBANX integration key.
    operationRequiredMust be enrollment.
    payment_type_codeRequiredMust be zalopay-recurring.

    Enrollment parameters​

    ParameterRequirementDescription
    enrollment.merchant_enrollment_codeRequiredUnique identifier for the enrollment. Required in every charge and to cancel the enrollment.
    enrollment.countryRequiredSet to vn for Vietnam.
    enrollment.nameOptionalFull name of the payer.
    enrollment.emailRequiredCustomer email.
    enrollment.phone_numberRequiredCustomer phone number.
    enrollment.back_urls.successRequiredURL to redirect the customer to after the agreement is confirmed in Zalopay.
    enrollment.back_urls.failureRequiredURL to redirect the customer to when the agreement is not confirmed.
  3. Enrollment request
    ​

    Enroll the customer Zalopay wallet using the /ws/userenrollment endpoint.

    Sample request

    Shell
    curl -X POST \
    --location 'https://sandbox.ebanxpay.com/ws/userenrollment' \
    --header 'Content-Type: application/json' \
    --data '{
    "integration_key": "{{integration_key}}",
    "operation": "enrollment",
    "payment_type_code": "zalopay-recurring",
    "enrollment": {
    "merchant_enrollment_code": "{{unique_enrollment_code}}",
    "country": "vn",
    "name": "John Doe",
    "email": "john.doe@example.com",
    "phone_number": "0812345678",
    "back_urls": {
    "success": "https://example.com/success",
    "failure": "https://example.com/failure"
    }
    }
    }'
  4. Enrollment response
    ​

    A successful request returns a JSON response like the one below.

    JSON
    {
    "status": "SUCCESS",
    "redirect_url": "{{redirect_url}}",
    "enrollment": {
    "merchant_enrollment_code": "{{unique_enrollment_code}}",
    "country": "vn",
    "name": "John Doe",
    "email": "john.doe@example.com",
    "phone_number": "0812345678",
    "back_urls": {
    "success": "https://example.com/success",
    "failure": "https://example.com/failure"
    }
    }
    }
    Customer experience
    • Desktop: Display the redirect_url as a QR code so the customer can scan it with their phone and confirm the agreement in the Zalopay app.
    • Mobile: Display the redirect_url as a link or button that opens the Zalopay app directly, where the customer confirms the agreement.
    • The merchant name is shown to the customer during the confirmation. Scanning the same QR code again after confirmation shows that the binding was already completed.
    • A customer can hold more than one enrollment.
    Important notes about enrollment
    • Redirect the customer to the redirect_url returned in the response so they can confirm the agreement in the Zalopay app.
    • The customer must confirm the agreement within 15 minutes. After that, the enrollment expires and a new enrollment must be created.
    • No amount is displayed to the customer during the agreement confirmation.
    Subsequent payments

    Save the merchant_enrollment_code. It is required in every payment request and to cancel the enrollment.

  5. Confirm the enrollment
    ​

    As soon as the customer confirms the agreement in the Zalopay app, the enrollment status changes from pending to accepted, and a Status Update Notification is sent to the URL defined in your merchant configuration.

    Notification example:

    Shell
    # Sent by EBANX to the merchant notification URL
    curl -X POST \
    --location 'https://merchant.example.com/callback' \
    --header 'Content-Type: application/x-www-form-urlencoded' \
    --data-urlencode 'operation=enrollment_status_change' \
    --data-urlencode 'notification_type=update' \
    --data-urlencode 'merchant_enrollment_code={{unique_enrollment_code}}'

    To ensure efficient and compliant interactions, EBANX allows merchants to confirm a user's enrollment status before sending multiple notifications or processing transactions.

  6. Query enrollment status
    ​

    Before requesting any payments, call the /ws/userenrollments/query endpoint using the merchant_enrollment_code to get the latest status of the enrollment.


    Sample request

    Shell
    curl -X POST \
    --location 'https://sandbox.ebanxpay.com/ws/userenrollments/query' \
    --header 'Content-Type: application/json' \
    --data '{
    "integration_key": "{{integration_key}}",
    "payment_type_code": "zalopay-recurring",
    "enrollment": {
    "merchant_enrollment_code": "{{unique_enrollment_code}}"
    }
    }'

    Sample response

    JSON
    {
    "status": "SUCCESS",
    "payment_type": "zalopay-recurring",
    "enrollment": {
    "status": "accepted"
    }
    }

    The response indicates one of the following values for the enrollment status:

    • accepted - Enrollment approved by the customer.
    • not_accepted - Enrollment rejected by the customer.
    • pending - Enrollment awaiting customer action.
    • expired - Enrollment expired.
    • not_found - Enrollment not found.
    • revoked - Enrollment cancelled.

    If your enrollment has the status accepted, you can start requesting recurring payments.

  7. Define payment parameters
    ​

    After the enrollment is accepted, create payments through /ws/direct. The payment follows the usual standards of /ws/direct requests, with the addition of the enrollment reference.

    ParameterRequirementDescription
    integration_keyRequiredYour EBANX integration key.
    operationRequiredSet to request.
    payment.nameOptionalFull name of the payer.
    payment.emailRequiredCustomer email.
    payment.countryRequiredSet to vn for Vietnam.
    payment.payment_type_codeRequiredSet to zalopay-recurring.
    payment.merchant_payment_codeRequiredUnique code for the payment.
    payment.currency_codeRequiredSet to VND.
    payment.amount_totalRequiredPayment amount in VND, a whole number between 1 and 20,000,000.
    payment.enrollment.merchant_enrollment_codeRequiredUnique identifier associated with the accepted enrollment.
  8. Create payment request
    ​

    Sample request

    Shell
    curl -X POST \
    --location 'https://sandbox.ebanxpay.com/ws/direct' \
    --header 'Content-Type: application/json' \
    --data '{
    "integration_key": "{{integration_key}}",
    "operation": "request",
    "payment": {
    "name": "John Doe",
    "email": "john.doe@example.com",
    "country": "vn",
    "payment_type_code": "zalopay-recurring",
    "merchant_payment_code": "{{unique_merchant_code}}",
    "currency_code": "VND",
    "amount_total": 100,
    "enrollment": {
    "merchant_enrollment_code": "{{unique_enrollment_code}}"
    }
    }
    }'
    Important

    The merchant_enrollment_code associated with the enrollment is required for every charge, and the enrollment must be accepted.

    Billing engine

    EBANX does not offer a recurring billing engine. Merchants are required to maintain their own charge schedules.

  9. Payment response
    ​

    A successful request returns a JSON response like the one below. The Zalopay Recurring payment has a pending (PE) status until it is confirmed.

    JSON
    {
    "payment": {
    "hash": "59acc5f00945fa382ab051651440826da7701533249b3a475",
    "country": "vn",
    "merchant_payment_code": "{{unique_merchant_code}}",
    "order_number": null,
    "status": "PE",
    "status_date": "{{YYYY-MM-DD HH:mm:ss}}",
    "open_date": "{{YYYY-MM-DD HH:mm:ss}}",
    "confirm_date": null,
    "transfer_date": null,
    "amount_br": "100.00",
    "amount_ext": "100.00",
    "amount_iof": "1",
    "currency_rate": "1",
    "currency_ext": "VND",
    "due_date": "{{YYYY-MM-DD}}",
    "instalments": "1",
    "payment_type_code": "zalopay-recurring",
    "pre_approved": false,
    "capture_available": null,
    "customer": {
    "email": "john.doe@example.com",
    "name": "John Doe"
    },
    "currency_ext_base": "VND"
    },
    "status": "SUCCESS"
    }
    Customer notifications

    Zalopay notifies the customer in the Zalopay app for every debit. No customer action is needed.

    Balance check

    Before each debit, EBANX checks with Zalopay if the customer's current balance is enough to pay the amount. If it's not, EBANX returns the response with a CA payment status, like in the example below, and the merchant is required to initiate a new charge.

    JSON
    {
    "payment": {
    "hash": "59acc5f00945fa382ab051651440826da7701533249b3a475",
    "payment_type_code": "zalopay-recurring",
    "status": "CA",
    "transaction_status": {
    "acquirer": "EBANX",
    "code": "NOK",
    "description": "Not accepted",
    "description_code": "NO_ACCEPTED"
    }
    },
    "status": "SUCCESS"
    }
  10. Monitor payment for status changes
    ​

    Notifications

    Status

  11. Congratulations!
    ​

    You have successfully integrated Zalopay Recurring.

    For more information, refer to the
    Direct API reference guidechevron_right

Cancel the enrollment​

Merchants can revoke an active subscription by sending a cancel request with the merchant enrollment code. EBANX unbinds the agreement in Zalopay, and no further payments can be charged to it.

Ensure the enrollment status is pending or accepted​

You can only cancel an enrollment if its status is pending or accepted. You can check the status using the /ws/userenrollments/query endpoint referencing its merchant_enrollment_code.

Sample request

Shell
curl -X POST \
--location 'https://sandbox.ebanxpay.com/ws/userenrollments/query' \
--header 'Content-Type: application/json' \
--data '{
"integration_key": "{{integration_key}}",
"payment_type_code": "zalopay-recurring",
"enrollment": {
"merchant_enrollment_code": "{{unique_enrollment_code}}"
}
}'

Sample response

JSON
{
"status": "SUCCESS",
"payment_type": "zalopay-recurring",
"enrollment": {
"status": "accepted"
}
}

Enrollments with a status of accepted or pending are eligible for cancellation. Other possible enrollment statuses include expired, not_found, and revoked, none of which can be cancelled.

Cancel the enrollment​

An enrollment can be revoked by either the customer (from within the Zalopay app) or the merchant. When the customer revokes it, EBANX sends a notification of the status change to the merchant, and the merchant can then query the enrollment status through the API to confirm it. For more information, refer to the Enrollment Notification page.

To cancel an enrollment, call the /ws/userenrollment endpoint (from your server) with the following required fields:

ParameterRequirementDescription
integration_keyRequiredYour unique and secret integration key.
operationRequiredMust be cancel.
enrollment.merchant_enrollment_codeRequiredThe unique enrollment identifier.

Sample request

Shell
curl -X POST \
--location 'https://sandbox.ebanxpay.com/ws/userenrollment' \
--header 'Content-Type: application/json' \
--data '{
"integration_key": "{{integration_key}}",
"operation": "cancel",
"enrollment": {
"merchant_enrollment_code": "{{unique_enrollment_code}}"
}
}'

Sample response

A successful request returns a JSON response with a status of revoked. Zalopay notifies the customer by SMS that the agreement was unbound.

JSON
{
"status": "SUCCESS",
"enrollment": {
"status": "revoked"
}
}

Refunds​

Zalopay Recurring supports full and partial refunds of confirmed payments.

  • Refunds can be requested up to 180 days after the payment is confirmed.
  • Most refunds are returned to the customer's Zalopay balance immediately; refunds involving banks can take 3-5 days.

Resources​

Use the following resources when testing in your sandbox environment.

Still need help?

Help Image

We hope this article was helpful. If you still have questions, you can explore the following options: