Server-to-server recurring payments

Subscription payment is a payment solution that supports periodic automatic deductions, helping you easily achieve automated recurring revenue collection. Buyers only need to complete a one-time authorization and binding to continuously enjoy subscription services, while also supporting flexible adjustments to subscription configurations (such as modifying cycles/amounts, canceling renewals, or terminating services). The entire process is secure, reliable, and user-friendly, balancing efficiency with transaction security.

This topic describes how to integrate card payment methods through the server-to-server integration mode, which is suitable for merchants with high requirements for payment process customization. This mode requires that you are PCI qualified. Please provide the relevant materials based on your business needs to complete the verification:

For more information about PCI DSS compliance requirements, see PCI DSS standard.

Note: This solution requires you to collect the buyer's card information independently. You must possess PCI compliance certification—please ensure you have completed PCI validation as required.

User experience

The following figures illustrates the user experience of the first-time subscription and subsequent deductions:

Note: When subscribing to payment services that require card binding, the first transaction requires the buyer completing the 3DS authentication process.

PC:

首次支付用户体验-pc.png

Mobile:

首次支付用户体验-mobile.png

Order lifecycle

The following diagram illustrates the process of subscription cycle, creating subscription, and receiving payment notifications.

The following diagram illustrates the entire subscription cycle, including subscription creation, payment method binding, completion of the first payment, and refund processing when necessary. The process ensures the valid activation of subscriptions and the security and transparency of all payment transactions.

1.png

2.png

Integration preparations

Before you start integrating, read the Integration Guide and API Overview documents to understand the integration steps of the server-side API and the precautions for calling the API. Furthermore, ensure that the following prerequisites are met:

  • Obtain a client ID
  • Complete the key configuration
  • Complete the configuration of paymentNotifyUrl to receive the asynchronous notification
  • Integrate the server-side SDK package, install the server-side library, and initialize a request instance. For more details, refer to Server-side SDKs.

Integration steps

Start your integration by taking the following steps:

  1. Add a list of payment methods
  2. Create a recurring payment request
  3. Redirect to 3D authentication page (normalUrl)
  4. Obtain the authorization result
  5. Capture and obtain the capture result
  6. Obtain the subscription notifications

Step 1: Add a list of payment methods Client-side

When the buyer enters the payment method selection page, the card brand identifiers and names to be integrated for this transaction should be displayed, allowing the buyer to choose according to their needs and preferences. 

NoteThe payment method list page needs to be implemented by you.

Step 2: Create an authorized recurring payment Server-side

You need to collect the buyer's payment information (for example, card number and exporation date), subscription information (for example, the deduction amount and cycle), order information, device information, and payment amount. Call the pay (One-time Payments) API, and submit an authorization subscription request for payment.

The subscription creation process includes the following key parameters:

Type

Parameter

Required

Description

Basic parameters

paymentRequestId

Yes

The request ID of the authorized payment, which must be unique each time.

paymentAmount.currency

Yes

The currency of the payment amount. 

paymentAmount.value

Yes

The payment amount. 

settlementStrategy.settlementCurrency

Yes

The currency of the settlement strategy for the payment request.

paymentMethod.paymentMethodType

Yes

Enumeration values for payment methods. In card payment scenario, the value is fixed as CARD. 

paymentFactor.isAuthorization

Yes

Payment mode. For card payment scenarios, the value is fixed as true, indicating the authorization-capture mode.

paymentFactor.captureMode

No

The capture mode. By default, Antom completes the capture for the merchant. If the initial subscription creation payment needs to be captured manually, you can set this field to MANUAL, and after authorization is successful, call the capture (One-time Payments) API.

productCode

Yes

Product code. In this scenario, the value is fixed as CASHIER_PAYMENT.

paymentRedirectUrl

Yes

The merchant page URL that the buyer is redirected to after the payment is completed. This URL must be an HTTPS address and should render content corresponding to the payment result.

paymentNotifyUrl

No

The URL that is used to receive the authorized payment result notification. This URL must be an HTTPS address and if the address is configured on Antom Dashboard, this parameter can be omitted. 

Device parameters

env.terminalType

Yes

Terminal type of which the merchant service applies to. Valid values are:

  • WEB: The client-side terminal type is a website, which is opened via a PC browser.
  • WAP: The client-side terminal type is an H5 page, which is opened via a mobile browser.
  • APP: The client-side terminal type is a mobile application.

env.clientIp

Yes

The IP address of the client device. 

Order parameters

order.orderAmount

Yes

The order amount of the merchant side.

order.referenceOrderId

Yes

The unique ID to identify the order on the merchant side.

order.orderDescription

Yes

The summary description of the order on the merchant side.

order.buyer

Yes

Buyer information. At least one of the following must be provided:

  • order.buyer.referenceBuyerId
  • order.buyer.buyerPhoneNo
  • order.buyer.buyerEmail

Subscription parameters

subscriptionInfo.subscriptionDescription

Yes

The description of the subscription.

subscriptionInfo.subscriptionStartTime

Yes

The date and time when the subscription becomes active.

subscriptionInfo.subscriptionNotifyUrl

Yes

The URL that is used to receive the subscription result notification. HTTPS address must be provided.

subscriptionInfo.trials

No

The list of trial information for the subscription. If your subscription service includes a trial or discount offer, you can specify this parameter.

subscriptionInfo.periodRule

Yes

The subscription period type, supporting four types of cycles: YEARMONTHWEEK and DAY.

Card payment parameters

paymentMethod.paymentMethodMetaData.cardNo

Yes

Card number. In card payment scenarios, if you collect card information yourself, you need to pass this parameter.

paymentMethod.paymentMethodMetaData.expiryYear

Yes

The year the card expires.In card payment scenarios, if you collect card information yourself, you need to pass this parameter.

paymentMethod.paymentMethodMetaData.expiryMonth

Yes

The month the card expires. In card payment scenarios, if you collect card information yourself, you need to pass this parameter.

paymentMethod.paymentMethodMetaData.cvv

Yes

Card verification code (CVV). In card payment scenarios, it is recommended to pass in the CVV information for first-time payments and when using a new card for subsequent payments, as this helps improve payment success rates.

paymentMethod.paymentMethodMetaData.cardholderName

Yes

Cardholder name. In card payment scenarios, if you collect card information yourself, you need to pass this parameter to improve payment success rate. This parameter supports only english characters.

paymentMethod.paymentMethodMetaData.

is3DSAuthentication

Yes

Determine whether to set the transaction authentication type to 3DS based on risk and dispute history. Valid values are:

  • true: indicates that the transaction authentication type is3DS authentication.
  • falseindicates that the transaction authentication type is Non-3DS authentication. Default if empty or not provided.

Note: In card payment scenarios, this parameter must be set as true to perform a 3D authentication, ensuring that the buyer’s identity is verified during the first card payment.

The following sample code shows how to call the pay (One-time Payments) API:

copy
public static void payByCardServer2Server() {
    AlipayPayRequest alipayPayRequest = new AlipayPayRequest();
    alipayPayRequest.setProductCode(ProductCodeType.CASHIER_PAYMENT);

    // Replace with your paymentRequestId
    String paymentRequestId = UUID.randomUUID().toString();
    alipayPayRequest.setPaymentRequestId(paymentRequestId);

    // Set subscription information
    SubscriptionInfo subscriptionInfo = new SubscriptionInfo();
    subscriptionInfo.setSubscriptionDescription("Subscription Description");
    LocalDateTime localNow = LocalDateTime.now();
    OffsetDateTime offsetDateTime = localNow.atOffset(ZoneOffset.of("+08:00"));
    String startTime = offsetDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
    subscriptionInfo.setSubscriptionStartTime(startTime);

    subscriptionInfo.setSubscriptionNotifyUrl("https://www.yourDomain.com/subscription/notify");
    PeriodRule periodRule = new PeriodRule();
    periodRule.setPeriodType("MONTH");
    periodRule.setPeriodCount(1);
    subscriptionInfo.setPeriodRule(periodRule);
    alipayPayRequest.setSubscriptionInfo(subscriptionInfo);

    // Set amount
    Amount amount = Amount.builder().currency("SGD").value("4200").build();
    alipayPayRequest.setPaymentAmount(amount);

    // Set payment method
    PaymentMethod paymentMethod = PaymentMethod.builder().paymentMethodType("CARD").build();
    alipayPayRequest.setPaymentMethod(paymentMethod);

    // Card information
    Map<String, Object> paymentMethodMetaData = new HashMap<>();
    paymentMethodMetaData.put("cardNo", "4054695723100768");
    paymentMethodMetaData.put("cvv", "123");
    paymentMethodMetaData.put("expiryMonth", "12");
    paymentMethodMetaData.put("expiryYear", "99");
    paymentMethodMetaData.put("tokenizeMode", false);
    paymentMethodMetaData.put("is3DSAuthentication", true);

    JSONObject cardholderName = new JSONObject();
    cardholderName.put("firstName", "John");
    cardholderName.put("lastName", "Doe");
    paymentMethodMetaData.put("cardholderName", cardholderName);
    paymentMethod.setPaymentMethodMetaData(paymentMethodMetaData);

    // Card payment information
    SettlementStrategy settlementStrategy = SettlementStrategy.builder().settlementCurrency("SGD").build();
    alipayPayRequest.setSettlementStrategy(settlementStrategy);

    // Replace with your orderId
    String orderId = UUID.randomUUID().toString();

    // Set buyer information
    Buyer buyer = Buyer.builder().referenceBuyerId("yourBuyerId").build();

    // Set order information
    Order order = Order.builder().referenceOrderId(orderId)
    .orderDescription("antom testing order").orderAmount(amount).buyer(buyer).build();
    alipayPayRequest.setOrder(order);

    // Set environment information
    Env env = Env.builder().terminalType(TerminalType.WEB).clientIp("1.2.3.4").build();
    alipayPayRequest.setEnv(env);

    // Set authorization payment mode
    PaymentFactor paymentFactor = PaymentFactor.builder().isAuthorization(true).build();
    alipayPayRequest.setPaymentFactor(paymentFactor);

    // Replace with your notification URL
    alipayPayRequest.setPaymentNotifyUrl("https://www.yourNotifyUrl.com");

    // Replace with your redirect URL
    alipayPay.setPaymentRedirectUrl("https://www.yourMerchantWeb.com");

    Execute payment
    AlipayPayResponse alipayPayResponse = null;
    try {
        alipayPayResponse = CLIENT.execute(alipayPayRequest);
    } catch (AlipayApiException e) {
        String errorMsg = e.getMessage();
        // Handle error message
    }
}

The following code shows a sample of the request message:

copy
{
    "subscriptionInfo": {
        "periodRule": {
            "periodCount": 1,
            "periodType": "MONTH"
        },
        "subscriptionDescription": "Subscription Description",
        "subscriptionNotifyUrl": "https://www.yourDomain.com/subscription/notify",
        "subscriptionStartTime": "2026-03-11T17:08:32.543+08:00"
    },
    "order": {
        "buyer": {
            "referenceBuyerId": "yourBuyerId"
        },
        "orderAmount": {
            "currency": "SGD",
            "value": "4200"
        },
        "orderDescription": "antom testing order",
        "referenceOrderId": "281c86fa-a780-4804-8d43-7737599843a8"
    },
    "env": {
        "clientIp": "1.2.3.4",
        "terminalType": "WEB"
    },
    "paymentAmount": {
        "currency": "SGD",
        "value": "4200"
    },
    "paymentFactor": {
        "isAuthorization": true
    },
    "paymentMethod": {
        "paymentMethodMetaData": {
            "cvv": "123",
            "cardholderName": {
                "firstName": "John",
                "lastName": "Doe"
            },
            "expiryMonth": "12",
            "expiryYear": "99",
            "cardNo": "4054695723100768",
            "is3DSAuthentication": true,
            "tokenizeMode": false
        },
        "paymentMethodType": "CARD"
    },
    "settlementStrategy": {
        "settlementCurrency": "SGD"
    },
    "paymentNotifyUrl": "https://www.yourNotifyUrl.com",
    "paymentRedirectUrl": "https://www.yourMerchantWeb.com",
    "paymentRequestId": "275624ab-5d33-43a5-9a4d-6e660bd00c43",
    "productCode": "CASHIER_PAYMENT"
}

The following code shows a sample of the response, which contains the following parameters:

  • result.resultStatusThe status of the authorized payment.
  • normalUrl: The URL used to redirect to 3D authentication page.
copy
{
    "normalUrl": "https://iexpfront-sea.alipay.com/authorise3d?merchantId=1887SjzZ56b1lvObJWQTaqG4PhACjUGncnl0Sihzg8en5I%3D&ddcId=m47qH0MLK7A7yJNVdjLYJOlH5V9T9z53bmrgN8gJXC0%3D&pass=lt%5E1&needDDC=Y&from=iexpcore",
    "paymentActionForm": "{\"method\":\"POST\",\"parameters\":\"{\\\"JWT\\\":\\\"eyJraWQiOiJDQVJESU5BTF9KV1RfYWxpcGF5IiwiY3R5IjoiQ0FSRElOQUxfSldUX2FsaXBheSIsImFsZyI6IkhTMjU2In0.eyJPcmdVbml0SWQiOiI1ZDVmMTA2MjRiNzkyYTFhZTBkN2Y3MTEiLCJSZWZlcmVuY2VJZCI6IjFmOTVkMTQ0LTcwZTAtNGFkYS1hZTY2LWViNjM2OWQ2YmMzMiIsIlJldHVyblVybCI6Imh0dHBzOi8vb3Blbi1uYS5hbGlwYXkuY29tL2FwaS9vcGVuL3VybF9jYWxsYmFja19nZXQvY2FyZGluYWwvY2FyZGluYWwwMDcuaHRtP3NpZ25EYXRhPVF0ZlolMkZuU0Y2dDVURTFJVnRRNkVycHQ3NSUyQjBxOE1zMlYxbW9iUm9WVnBlMUdpYlVTNFJBWEJldDNNJTJCNHRFWDU5OEx0JTJCVzhUem1XRXRpelc0czdRQVhLRGZwajJDSG5MUHRjQSUyRjNIT3Z6TXp1JTJCSzM1NSUyQnBLS1h3eXNSVkU4cTJVaUZWTzhMRTVWTE5JZ0paRjdVQUZMWVE2RElmSmNKenFVNFBUMm1oaTRDT3ZEZVJ3NFVNSzRWWjA4U1M0SnJ2Yk9teXhHaXgwV2tJUEdJSk5RcVpUVyUyQnhyZFAlMkJSMXhNMCUyRjVlejVqc2JkTHowMmFJNGp0SzFLanU4d3pvNnBxVWxrNXU4dm1jb1d5MHI0dlJwRUslMkJHbmJNM0o4eUxuWmNDNER5TiUyRjIlMkJIVGFiYnBpRFdWSGZRbjF5QyUyQlZ3ZE4yN25xcHl5RTBVc0ZDaFhveEFaTDQ5NkElM0QlM0QmaW5TZXJpYWxObz0yMDI2MDMxMzg5MDMxMzQxMDAwN0Q2MjQyODI5MDY0Jm91dE9yZGVyTm89MjAyNjAzMTM4OTAzMTM0MTAwMDdENjI0MjgyOTA2NCIsImV4cCI6MTc3MzM2ODMxOSwiaWF0IjoxNzczMzY3NDE5LCJpc3MiOiI1ZDVmZGRlOGNjZWY3NjFhZDhiODJiNjgiLCJqdGkiOiIyMDI2MDMxMzg5MDMxMzQxMDAwN0Q2MjQyODI5MDY0In0.D1yRpHR9TwvCkBlcqtGSjpj9fjphqV_FGDnls8AnuoI\\\",\\\"Bin\\\":\\\"405469\\\",\\\"DDCFlag\\\":\\\"Y\\\"}\",\"paymentActionFormType\":\"RedirectActionForm\",\"redirectUrl\":\"https://iexpfront-sea.alipay.com/authorise3d?merchantId=1887SjzZ56b1lvObJWQTaqG4PhACjUGncnl0Sihzg8en5I%3D&ddcId=m47qH0MLK7A7yJNVdjLYJOlH5V9T9z53bmrgN8gJXC0%3D&pass=lt%5E1&needDDC=Y&from=iexpcore\"}",
    "paymentAmount": {
        "currency": "SGD",
        "value": "4200"
    },
    "paymentCreateTime": "2026-03-12T19:03:38-07:00",
    "paymentId": "202603131940108****01887D0280499441",
    "paymentRequestId": "d6af2336-3cd6-4a17-94a2-10d44386****",
    "paymentResultInfo": {
        "cardBrand": "VISA",
        "cardNo": "************0768",
        "credentialTypeUsed": "PAN",
        "threeDSResult": {

        }
    },
    "paymentTime": "2026-03-12T19:03:39-07:00",
    "redirectActionForm": {
        "method": "POST",
        "parameters": "{\"JWT\":\"eyJraWQiOiJDQVJESU5BTF9KV1RfYWxpcGF5IiwiY3R5IjoiQ0FSRElOQUxfSldUX2FsaXBheSIsImFsZyI6IkhTMjU2In0.eyJPcmdVbml0SWQiOiI1ZDVmMTA2MjRiNzkyYTFhZTBkN2Y3MTEiLCJSZWZlcmVuY2VJZCI6IjFmOTVkMTQ0LTcwZTAtNGFkYS1hZTY2LWViNjM2OWQ2YmMzMiIsIlJldHVyblVybCI6Imh0dHBzOi8vb3Blbi1uYS5hbGlwYXkuY29tL2FwaS9vcGVuL3VybF9jYWxsYmFja19nZXQvY2FyZGluYWwvY2FyZGluYWwwMDcuaHRtP3NpZ25EYXRhPVF0ZlolMkZuU0Y2dDVURTFJVnRRNkVycHQ3NSUyQjBxOE1zMlYxbW9iUm9WVnBlMUdpYlVTNFJBWEJldDNNJTJCNHRFWDU5OEx0JTJCVzhUem1XRXRpelc0czdRQVhLRGZwajJDSG5MUHRjQSUyRjNIT3Z6TXp1JTJCSzM1NSUyQnBLS1h3eXNSVkU4cTJVaUZWTzhMRTVWTE5JZ0paRjdVQUZMWVE2RElmSmNKenFVNFBUMm1oaTRDT3ZEZVJ3NFVNSzRWWjA4U1M0SnJ2Yk9teXhHaXgwV2tJUEdJSk5RcVpUVyUyQnhyZFAlMkJSMXhNMCUyRjVlejVqc2JkTHowMmFJNGp0SzFLanU4d3pvNnBxVWxrNXU4dm1jb1d5MHI0dlJwRUslMkJHbmJNM0o4eUxuWmNDNER5TiUyRjIlMkJIVGFiYnBpRFdWSGZRbjF5QyUyQlZ3ZE4yN25xcHl5RTBVc0ZDaFhveEFaTDQ5NkElM0QlM0QmaW5TZXJpYWxObz0yMDI2MDMxMzg5MDMxMzQxMDAwN0Q2MjQyODI5MDY0Jm91dE9yZGVyTm89MjAyNjAzMTM4OTAzMTM0MTAwMDdENjI0MjgyOTA2NCIsImV4cCI6MTc3MzM2ODMxOSwiaWF0IjoxNzczMzY3NDE5LCJpc3MiOiI1ZDVmZGRlOGNjZWY3NjFhZDhiODJiNjgiLCJqdGkiOiIyMDI2MDMxMzg5MDMxMzQxMDAwN0Q2MjQyODI5MDY0In0.D1yRpHR9TwvCkBlcqtGSjpj9fjphqV_FGDnls8AnuoI\",\"Bin\":\"405469\",\"DDCFlag\":\"Y\"}",
        "redirectUrl": "https://iexpfront-sea.alipay.com/authorise3d?merchantId=1887SjzZ56b1lvObJWQTaqG4PhACjUGncnl0Sihzg8en5I%3D&ddcId=m47qH0MLK7A7yJNVdjLYJOlH5V9T9z53bmrgN8gJXC0%3D&pass=lt%5E1&needDDC=Y&from=iexpcore"
    },
    "result": {
        "resultCode": "PAYMENT_IN_PROCESS",
        "resultMessage": "payment in process",
        "resultStatus": "U"
    }
}

The table below shows the possible values of result.resultStatus in the response. Please handle the result according to the guidance provided:

result.resultStatus

Message

Further action

S

The authorization is successful. The order can be proceeded with capture, and you need to store paymentId for subsequent capture or refund operations.

F

The authorization failed. Please close the current transaction or retry with a new paymentRequestId.

U

The authorization is being processed.
  • If normalUrl is returned, it indicates that 3DS authentication is required. The merchant client should redirect to normalUrl while storing the paymentId for subsequent capture or refund operations.
  • If normalUrl is not returned, it means the transaction is being processed. You can choose to actively call the inquiryPayment API to obtain the authorization result or wait for the notifyPayment (Subscription) API to send asynchronous notification, and store the paymentId for subsequent capture or refund operations.

Note: If no response is received, it may indicate a network timeout. You can call the inquiryPayment API to query the authorization result, or wait for the notifyPayment (Subscription) API to send asynchronous notifications.

Common questions

Q: Is 3D authentication mandatory for the first transaction?

A: The first transaction is a buyer-initiated transaction that requires identity verification to ensure the security of subsequent periodic deductions when the buyer is not present. Therefore, the following requirements apply to identity verification for the first transaction:

  • Complete 3D authentication through Antom: When the buyer selects card payment and paymentMethodType is set to CARD, the 3DS authentication must be completed to verify the buyer’s identity. You can set the is3DSAuthentication parameterto true in the pay (One-time Payments) API request to complete the authentication through Antom 3DS.
  • Complete 3D authentication through a third-party institution: In the server-to-server mode, after you collect the card information yourself and complete 3DS authentication through a third-party institution, you need to provide the mpiData.eci and mpiData.cavv parameters.

Step 3: Redirect to 3D authentication page (normalUrl) Client-side

After the merchant server obtains normalUrl from Antom and passes it to the frontend, it will redirect from the merchant frontend to the 3D authentication page. 

The following is sample codes for the merchant front end to load normalUrl.

copy
if (serverResponse.normalUrl != null) {
    window.open(serverResponse.normalUrl, '_blank');
}

The following images demonstrate the user experience of redirecting to the 3D authentication page on Web or App platforms.

3.png

Common questions

QHow to handle different payment experiences?

A: The normalUrl  returned from card payments integrated via API is an H5 link. For WEB and WAP scenarios, you can directly redirect to this URL. For APP scenarios, it is recommended to load the normalUrl through a WebView to enhance the user experience.

Q: What should be noted when passing the paymentRedirectUrl parameter?

A: paymentRedirectUrl is set to an HTTPS address by default. Additionally, do not encode special characters in the URL, as this may cause abnormalities in the payment process.

Q: How should the payment result page be displayed?

A: You need to pass an HTTPS address via the paymentRedirectUrl parameter in the pay (One-time Payments)API request to display the payment result on the merchant side.Whether the subscription creation succeeds or fails, the buyer may return to the merchant page from the payment method side. Do not set paymentRedirectUrl to a fixed "subscription payment success page." Instead, you should the display result page based on the actual results returned by the server to avoid misleading the buyer.

Q: Does returning to the merchant result page indicate that the subscription payment was created successfully?

A: You cannot determine whether the subscription was created successfully solely based on the redirection to the merchant page. Key reasons include:

  • After the buyer successfully creates a subscription payment, they may fail to return to the merchant page due to network abnormalities or other reasons.
  • Even if the buyer does not complete the subscription payment, they may still return to the merchant page via the payment method side.
  • Even if the buyer completes the subscription payment authentication, the subscription will not take effect if the initial deduction fails.

Step 4: Obtain the authorization result Server-side

Whether the authorization succeeds or fails, Antom will send the authorized payment result to you through the notifyPayment (Subscription) API. Please follow the steps below to obtain the authorized payment result notification.

  1. Configure the webhook URL to receive asynchronous notifications: Set the paymentNotifyUrl parameter in the pay (One-time Payments) API request or configure the webhook URL in the Antom Dashboard.

The following code shows a sample of the asynchronous notification request:

copy
{
  "notifyType": "PAYMENT_RESULT",
  "result": {
    "resultCode": "SUCCESS",
    "resultStatus": "S",
    "resultMessage": "success"
  },
  "paymentRequestId": "2020010123456789XXXX",
  "paymentId": "2020010123456789XXXX",
  "paymentAmount": {
    "value": "8000",
    "currency": "EUR"
  },
  "paymentCreateTime": "2020-01-01T12:01:00+08:30",
  "paymentTime": "2020-01-01T12:01:01+08:30"
}
  1. Verify the asynchronous notification

If you receive an asynchronous notification from Antom, you are required to return the response in the Sample code format, but you do not need to countersign the response.

You need to verify the signature of the payment notification sent by Antom.

copy
 /**
     * receive payment notify
     *
     * @param request    request
     * @param notifyBody notify body
     * @return Result
     */
    @PostMapping("/receivePaymentNotify")
    @ResponseBody
    public Result receivePaymentNotify(HttpServletRequest request, @RequestBody String notifyBody) {
        // retrieve the required parameters from http request
        String requestUri = request.getRequestURI();
        String requestMethod = request.getMethod();

        // retrieve the required parameters from request header
        String requestTime = request.getHeader("request-time");
        String clientId = request.getHeader("client-id");
        String signature = request.getHeader("signature");

        try {
            // verify the signature of notification
            boolean verifyResult = WebhookTool.checkSignature(requestUri, requestMethod, clientId,
                    requestTime, signature, notifyBody, ANTOM_PUBLIC_KEY);
            if (!verifyResult) {
                throw new RuntimeException("Invalid notify signature");
            }

            // deserialize the notification body
            AlipaySubscriptionPayNotify paymentNotify = JSON.parseObject(notifyBody, AlipaySubscriptionPayNotify.class);

            if (paymentNotify != null && "SUCCESS".equals(paymentNotify.getResult().getResultCode())) {
                // handle your own business logic.
                // e.g. The payment information of the user is saved through the relationship between the subscriptionRequestId and the user ID.
                System.out.println("receive payment notify: " + JSON.toJSONString(paymentNotify));
                return Result.builder().resultCode("SUCCESS").resultMessage("success.").resultStatus(ResultStatusType.S).build();
            }

        } catch (Exception e) {
            return Result.builder().resultCode("FAIL").resultMessage("fail.").resultStatus(ResultStatusType.F).build();
        }

        return Result.builder().resultCode("SYSTEM_ERROR").resultMessage("system error.").resultStatus(ResultStatusType.F).build();
    }
  1. Respond to the notification result. When responding to the notification, you must follow the fixed format below regardless of whether the payment transaction succeeds or fails, and no signature processing is required.
copy
{
    "result": {
        "resultCode": "SUCCESS",
        "resultStatus": "S",
        "resultMessage": "success"
    }
}

Step 5: Capture and obtain the capture result Server-side

Note: Capture will be triggered only when the authorized payment is successful.

If payment is successful, Antom will automatically initiate capture for you, while you can also initiate capture manually. Antom will send the capture result notification to you through the notifyCapture (One-time Payments) API, while you can also query the capture result. You need to decide whether to ship goods based on the capture result. For detailed steps, refer to Capture.

Step 6: Obtain subscription notifications Server-side

After the subscription becomes effective, Antom will send you the following subscription notifications:

First-time subscription notification

Antom will send the following event notifications via HTTPS to the webhook you configured in the API or in the Antom Dashboard.

Notification type

API

Request message

Subscription status notification

After the subscription takes effect, Antom will send the subscription result notification to you through the notifySubscription API.

  • When the value of subscriptionNotificationType in the request message is CREATE, subscriptionStatus indicates whether the subscription is active (ACTIVE) or terminated (TERMINATED).
  • When the value of subscriptionNotificationType is TERMINATE, the subscription is terminated.

Current deduction result notification

After a subscription payment is successfully activated or renewed, Antom will send the current subscription deduction result to you through the notifyPayment (Subscription) API.

When the value of notifyType in the request message is PAYMENT_RESULT, the result.resultStatus parameter indicates whether the current deduction is successful (S) or failed (F).

Obtain the subcription status notification by following the steps below:

  1. Set the webhook URL to receive notifications: Configure through the subscriptionInfo.subscriptionNotifyUrl  parameter in the pay (One-time Payments) API. The following are examples of notifications for the two subscription statuses:
  • When the value of subscriptionNotificationType in the request message is CREATE, subscriptionStatus indicates the status of the subscription:
  • ACTIVE: indicates that the subscription is active.
  • TERMINATEDindicates that the subscription is terminated.
copy
{
  "periodRule": {
    "periodCount": 1,
    "periodType": "MONTH"
  },
  "subscriptionEndTime": "2074-02-20T01:16:17-08:00",
  "subscriptionId": "20240914190000000000000050000010226",
  "subscriptionNotificationType": "CREATE",
  "subscriptionRequestId": "SUBSCRIPTION_202444091410165oo009851_AUTO",
  "subscriptionStartTime": "2024-09-13T19:30:17-07:00",
  "subscriptionStatus": "ACTIVE"
}
  • When the value of subscriptionNotificationType is TERMINATE, the subscription is terminated.
copy
{
  "periodRule": {
    "periodCount": 1,
    "periodType": "WEEK"
  },
  "subscriptionId": "2025102619******00000160000671943",
  "subscriptionLastUpdateTime": "2025-10-26T09:51:13-07:00",
  "subscriptionNotificationType": "TERMINATE",
  "subscriptionRequestId": "PR_en_****176",
  "subscriptionStartTime": "2025-10-26T10:01:13-07:00",
  "subscriptionStatus": "TERMINATED"
}
  1. Verify the asynchronous notification

If you receive an asynchronous notification from Antom, you are required to return the response in the Sample code format, but you do not need to countersign the response.

You need to verify the signature of the payment notification sent by Antom.

copy
/**
     * receive subscription notify
     *
     * @param request    request
     * @param notifyBody notify body
     * @return Result
     */
    @PostMapping("/receiveSubscriptionNotify")
    @ResponseBody
    public Result receiveSubscriptionNotify(HttpServletRequest request, @RequestBody String notifyBody) {
        // retrieve the required parameters from http request
        String requestUri = request.getRequestURI();
        String requestMethod = request.getMethod();

        // retrieve the required parameters from request header
        String requestTime = request.getHeader("request-time");
        String clientId = request.getHeader("client-id");
        String signature = request.getHeader("signature");

        try {
            // verify the signature of notification
            boolean verifyResult = WebhookTool.checkSignature(requestUri, requestMethod, clientId,
                    requestTime, signature, notifyBody, ANTOM_PUBLIC_KEY);
            if (!verifyResult) {
                throw new RuntimeException("Invalid notify signature");
            }

            // deserialize the notification body
            AlipaySubscriptionNotify subscriptionNotify = JSON.parseObject(notifyBody, AlipaySubscriptionNotify.class);

            if (subscriptionNotify != null && SubscriptionNotificationType.CREATE.equals(subscriptionNotify.getSubscriptionNotificationType())) {
                // handle your own business logic.
                // e.g. The subscription information of the user is saved through the relationship between the subscriptionRequestId and the user ID.
                System.out.println("receive subscription notify: " + JSON.toJSONString(subscriptionNotify));
                return Result.builder().resultCode("SUCCESS").resultMessage("success.").resultStatus(ResultStatusType.S).build();
            }

        } catch (Exception e) {
            return Result.builder().resultCode("FAIL").resultMessage("fail.").resultStatus(ResultStatusType.F).build();
        }

        return Result.builder().resultCode("SYSTEM_ERROR").resultMessage("system error.").resultStatus(ResultStatusType.F).build();
    }
  1. Respond to the notification result. When responding to the notification, you must follow the fixed format below regardless of whether the payment transaction succeeds or fails, and no signature processing is required.
copy
{
    "result": {
        "resultCode": "SUCCESS",
        "resultStatus": "S",
        "resultMessage": "success"
    }
}

Common questions

Q: Will the asynchronous notification be re-sent?

A: Yes, the asynchronous notification will be re-sent automatically within 24 hours for the following cases:
  • If you didn't receive the asynchronous notification due to network reasons.
  • If you receive an asynchronous notification from Antom, but you didn't make a response to the notification in the sample code format of Process the notification.

The notification can be resent up to 8 times or until a correct response is received to terminate delivery. The sending intervals are as follows: 0 minutes, 2 minutes, 10 minutes, 10 minutes, 1 hour, 2 hours, 6 hours, and 15 hours.

Q: Do I need to verify the signature after receiving the payment result notification?

A: Yes. To ensure that the callback request is indeed sent by Antom, verification is required during signature verification. When assembling messages to be verified, you are required to process in the following standard order: <http-method> <http-uri> <client-id>.<request-time>.<request-body>. Note that <request-body> should be assembled directly from the original value, rather than parsed into JSON and then assembled.

Q: If the first payment fails, will the subscription take effect?

A: When the first deduction during subscription activation fails, the subscription will not take effect. Antom will push a webhook notification with subscriptionStatus=TERMINATED to you, indicating that the subscription activation has failed.

Q: What is the subscription timeout period?

A: For card payments, the default timeout period is 7 days. You can also specify a timeout period through the subscriptionExpiryTime field.

Q: What are the differences between the current deduction result notification, the authorization notification, and the capture notification?

A: The authorized payment notification includes information related to 3DS authentication and other authorization details. The capture notification reflects the final payment result and is recommended as the basis for shipment. The current deduction result notification contains subscription cycle information, including the start time, end time, and period number of the current cycle.

Q: Can I initiate manual capture for the first payment of the subscription?

A: Yes. You need to set the value of the paymentFactor.captureMode field to MANUAL in the pay (One-time Payments) API, and then call the capture (One-time Payments) API manually after receiving the asynchronous notification of successful authorized payment. In this scenario, the first payment is only considered successful when the capture is successful, and only then will Antom generate the subsequent periodic deduction plan.

Subscription renewal notification

After the subscription is successfully created and has taken effect, the Antom system will automatically initiate renewal deductions based on the subscription rules you have configured, and send the corresponding payment result notifications through webhook to achieve periodic deductions. The timing and rules for triggering renewal deductions are as follows:

  • Trigger timing: The renewal deduction will be automatically triggered 24 hours before the start date of the next subscription cycle. You can determine the initiation time of the next cycle’s payment by subtracting 24 hours from the periodEndTime parameter in the previous cycle’s payment result notification.
  • Cycle rule: The renewal period and deduction frequency will follow the periodRule defined during subscription creation, for example, daily, monthly, quarterly, or yearly billing. The explanation is as follows:

First payment deduction time

(paymentTime = periodStartTime)

Subscription cycle

Next payment deduction time (paymentTime)

Next Period Effective Time (periodStartTime)

2024-09-25T20:10:17+08:00

To set the subscription cycle to 3 days, you need to pass in the following parameters:

  • periodRule.periodCount = 3
  • periodRule.periodType = DAY

2024-09-27T20:20:04+08:00

2024-09-28T20:10:17+08:00

2024-09-25T20:10:17+08:00

To set the subscription cycle to one week, you need to pass in the following parameters:

  • periodRule.periodCount = 1
  • periodRule.periodTypeWEEK

2024-10-01T20:10:17+08:00

2024-10-02T20:10:17+08:00

2024-09-25T20:10:17+08:00

To set the subscription cycle to one month, you need to pass in the following parameters:

  • periodRule.periodCount = 1
  • periodRule.periodTypeMONTH

2024-10-24T20:10:17+08:00

2024-10-25T20:10:17+08:00

2024-09-25T20:10:17+08:00

To set the subscription cycle to one year, you need to pass in the following parameters:

  • periodRule.periodCount = 1
  • periodRule.periodTypeYEAR

2025-09-24T20:10:17+08:00

2025-09-25T20:10:17+08:00

Asynchronous notifications are generally divided into the following scenarios:

Common scenarios

First-time subscription payment

Subsequent periodic deductions

Authorized payment result notification

A notification is sent to inform you of the authorization result and the 3DS authentication result.

A notification is sent to inform you of the authorization result and the 3DS authentication result.

Capture result notification

Authorization success will trigger a notification, and the capture result serves as the basis for shipping.Authorization success will trigger a notification, and the capture result serves as the basis for shipping.

Subscription status notification

A notification is sent to inform you of the subscription creation result.

No notification is sent.

Current deduction result notification

A notification is sent to inform you of the current deduction result, amount, and validity period.

A notification is sent to inform you of the current deduction result, amount, and validity period.

The following are sample codes for asynchronous notifications in each scenario:

copy
{
  "notifyType": "PAYMENT_RESULT",
  "result": {
    "resultCode": "SUCCESS",
    "resultStatus": "S",
    "resultMessage": "success"
  },
  "paymentRequestId": "2020010123456789XXXX",
  "paymentId": "2020010123456789XXXX",
  "paymentAmount": {
    "value": "8000",
    "currency": "EUR"
  },
  "paymentCreateTime": "2020-01-01T12:01:00+08:30",
  "paymentTime": "2020-01-01T12:01:01+08:30"
}

Common questions

Q: If a payment deduction fails, will it cause the subscription to become invalid?

A: If the first deduction when creating a subscription fails, the subscription will not take effect. However, if the subscription has already taken effect and subsequent cycle deductions fail (for example, due to insufficient balance), the subscription will remain valid. If the subscription is not actively canceled, Antom will retry the deduction in the next billing cycle.

Q: Will a payment failure trigger a deduction notification, and will it be retried?

A: A deduction failure notification will be sent when the deduction fails. In card recurring payment scenarios, Antom will not initiate a retry. If you need to initiate a retry, please contact Antom technical support for a customized solution.

Q: If the first deduction occurs on dates such as February 28, March 31, or April 30 (the end of the month), how is the next deduction date determined?

A: The subscription cycle logic is based on the selected date. If the next cycle does not have that same date, the deduction will occur one day earlier. For example:

  • First cycle: 1.28, Second cycle: 2.28, Third cycle: 3.28, Fourth cycle: 4.28.
  • First cycle: 1.31, Second cycle: 2.28, Third cycle: 3.31, Fourth cycle: 4.30.
  • First cycle: 1.30, Second cycle: 2.28, Third cycle: 3.30, Fourth cycle: 4.30.

Q: How are subsequent recurring deductions associated with the initial subscription?

A: For recurring deduction notifications, the association can be made using the subscriptionRequestId or subscriptionId included in the notification request to link back to the initial subscription. For recurring deductions in card payment scenarios, Antom will additionally send authorization result notifications and capture result notifications. These two notifications can be linked to the recurring deduction notification through paymentId, and ultimately associated with the initial subscription’s subscriptionId.

Q: For card payments, if I initiate the capture manually when the subscription is first created (by setting paymentFactor.captureMode to MANUAL), how will subsequent automatic deduction be handled?

A: All subsequent automatic renewals are initiated by Antom without merchant involvement in the capture process, which means the value of the paymentFactor.captureMode parameter defaults to AUTOMATIC.

After subscription

After completing the subscription, you can perform the following actions:

Subscription trial

Antom provides a subscription trial feature that allows buyers to experience a product or service for a limited time at no cost or at a discounted rate before officially purchasing a subscription plan.
For more details, please refer to the Subscription trial.

Subscription cancellation

The subscription cancellation feature allows buyers to cancel their current subscription at any time when they no longer need to use the associated service. For more details, please refer to the Subscription cancellation.

Cancellation Server-side

For successful payments, if the buyer requests cancellation or a refund on the same day, you can use Antom’s cancellation capability to cancel the order or release funds. Orders not yet completed can also be cancelled directly. For details, refer to Cancel.

Refund Server-side

For successfully paid orders, if you need to issue a refund to the buyer, Antom provides two methods:

  • Your operations staff can manually process refunds directly through the Antom Dashboard.
  • Call the refund API to initiate a refund. 

Antom’s refund capabilities are as follows:

  • Supports full refunds.
  • Supports multiple partial refunds, with the total refunded amount not exceeding the captured amount.

Refer to Refund to learn about Antom refund rules and operation process.

Best practices

Antom provides you with the following best practice solutions: