Welcome to Xendit’s latest documentation. For legacy content, access the previous version here.

Pay and Save

Prev Next

Integrating payments and securely saving card details on your checkout page is straightforward with our "Pay and Save" flow. This allows your customers to store their card information for future transactions during a single end-user journey.

Pay and save flow (with authentication)

  1. Create a payment session

Request - POST /sessions

{
    "reference_id": "YOUR_PAYMENT_REFERENCE_ID",
    "session_type": "PAY",
    "mode": "CARDS_SESSION_JS",
    "amount": 100000,
    "currency": "IDR",
    "country": "ID",
    "customer": {
        "reference_id": "YOUR_CUSTOMER_REFERENCE",
        "type": "INDIVIDUAL",
        "email": "test@yourdomain.com",
        "mobile_number": "+6212345678",
        "individual_detail": {
            "given_names": "Jaap",
            "surname": "Stam"
        }
    },
    "cards_session_js": {
        "success_return_url": "https://yourcompany.com/success",
        "failure_return_url": "https://yourcompany.com/failure"
    }
}

Response - POST /sessions

{
    "payment_session_id": "ps-6746c1006b7752b4d91725af",
    "created": "2024-11-27T06:49:36.535Z",
    "updated": "2024-11-27T06:49:36.535Z",
    "status": "ACTIVE",
    "reference_id": "YOUR_PAYMENT_REFERENCE_ID",
    "currency": "IDR",
    "amount": 10000,
    "country": "ID",
    "customer_id": "XENDIT_GENERATED_CUSTOMER_ID",
    "expires_at": "2024-11-27T07:19:36.434Z",
    "session_type": "PAY",
    "mode": "CARDS_SESSION_JS",
    "locale": "en",
    "business_id": "YOUR_BUSINESS_ID", 
    "cards_session_js": {
        "success_return_url": "https://yourcompany.com/success",
        "failure_return_url": "https://yourcompany.com/failure"
    }
}
  1. Gather the card information

    Initiate card_session.js on your page. Collect the card information using Xendit.payment.collectCardData(reqData, xenditResponseHandler). This will collect the card information and send it to Xendit using card_session.js.

Request - to card_session_js

{
    "card_number": "4000000000001091",
    "expiry_month": "12",
    "expiry_year": "2040",
    "cvn": "123",
    "cardholder_first_name": "Firstname",
    "cardholder_last_name": "Last",
    "cardholder_email": "shopper@emailaddress.com",
    "cardholder_phone_number": "+61111111111",
    "save_card_payment_token": true,
    "payment_session_id": "YOUR_PAYMENT_SESSION_ID"
}

Response - from card_session_js

{ 
    "message": "Status updated. Wait for a callback or get the status using the Get API.", 
    "payment_request_id": "PAYMENT_REQUEST_ID", 
    "action_url": "AUTHENTICATION_PAGE_URL" 
}

Important: Store the payment_request_id. You'll need it to retrieve the transaction's status and it will be included in the webhook for status updates.

  1. Redirect to the authentication page

    Redirect customer to the authentication page using the action_url from the response object.

  2. Customer completes authentication

    After successful authentication, your cusotmer is redirected to your success_return_url. If authentication fails, they may be redirected to your failure_return_url.

  3. Receive the webhook

    Xendit sends a payment webhook to your webhook endpoint indicating the final state of the transaction. You can match the payment webhook with the payment_request_id.

Example payment.capture webhook:

{
    "created": "2024-12-18T05:46:35.109Z",
    "business_id": "62440e322008e87fb29c1fd0",
    "event": "payment.capture",
    "data": {
        "type": "PAY",
        "status": "SUCCEEDED",
        "country": "ID",
        "created": "2024-12-18T05:46:08.192Z",
        "updated": "2024-12-18T05:46:30.627Z",
        "captures": [
            {
                "capture_id": "cptr-08f17fa3-e80c-4d8e-8c34-17aa3400bc1c",
                "capture_amount": 10000,
                "capture_timestamp": "2024-12-18T05:46:34.234Z"
            }
        ],
        "currency": "IDR",
        "payment_id": "py-3f57d678-2448-4c9f-a433-8468d366fb5c",
        "business_id": "62440e322008e87fb29c1fd0",
        "customer_id": "cust-7de9a9b4-37e8-40ad-b665-d97f42e538c5",
        "channel_code": "CARDS",
        "reference_id": "97ba0a32-b996-4abf-8a7b-6184a6644676_b8d18f2f-3",
        "capture_method": "AUTOMATIC",
        "request_amount": 10000,
        "payment_details": {
            "authorization_data": {
                "reconciliation_id": "7345007929096981703954",
                "authorization_code": "831000",
                "acquirer_merchant_id": "xendit_ctv_agg",
                "network_response_code": "00",
                "network_transaction_id": "016153570198200",
                "cvn_verification_result": "M",
                "retrieval_reference_number": "435205253972",
                "address_verification_result": "M",
                "network_response_code_descriptor": "Approved and completed sucessfully"
            },
            "authentication_data": {
                "flow": "CHALLENGE",
                "a_res": {
                    "eci": "05",
                    "message_version": "2.1.0",
                    "authentication_value": "AAIBBYNoEwAAACcKhAJkdQAAAAA=","directory_server_trans_id": "e537f539-d59f-4ebe-8d56-7fdc31a8e9b4"
                }
            }
        },
        "payment_request_id": "pr-5593127f-8c7b-4d2f-b487-c785ffc21e2f",
        "payment_token_id": "pt-8983127f-98er-76dr-67er-d985gff21z2f"
    },
    "api_version": "v3"
}

It's recommended to save the payment_id and payment_details from the webhook, correlated with the payment_request_id, as proof of payment.

Pay and Save without authentication

We highly recommend performing authentication when processing card transactions. Authentication does not only provide a liability shift (protecting you from certain fraud disputes), it additionally ensures users are who they say they are.

However, upon request and after a risk-based assessment, Xendit can activate guest checkout without mandating authentication. We’ll perform a risk-based assessment, before allowing you to accept transactions without authentication.

Pay and Save flow without authentication

  1. Create a payment session

Request - POST /sessions

{
    "reference_id": "YOUR_PAYMENT_REFERENCE_ID",
    "session_type": "PAY",
    "mode": "CARDS_SESSION_JS",
    "amount": 10000,
    "currency": "IDR",
    "channel_properties": {
        "cards": {
            "skip_three_ds": true
        }
    },
    "country": "ID",
    "customer": {
        "reference_id": "YOUR_CUSTOMER_REFERENCE",
        "type": "INDIVIDUAL",
        "email": "test@yourdomain.com",
        "mobile_number": "+6212345678",
        "individual_detail": {
            "given_names": "Lorem",
            "surname": "Ipsum"
        }
    },
    "cards_session_js": {
        "success_return_url": "https://yourcompany.com/success",
        "failure_return_url": "https://yourcompany.com/failure"
    }
}

Response- POST /sessions

{
    "payment_session_id": "UNIQUE_PAYMENT_SESSION_ID",
    "created": "2024-11-28T05:07:14.585Z",
    "updated": "2024-11-28T05:07:14.585Z",
    "status": "ACTIVE",
    "reference_id": "new-uuid8",
    "currency": "IDR",
    "amount": 10000,
    "country": "ID",
    "customer_id": "cust-f945d691-2101-4582-890a-12afd211ea12",
    "expires_at": "2024-11-28T05:37:14.390Z",
    "session_type": "PAY",
    "mode": "CARDS_SESSION_JS",
    "locale": "en",
    "business_id": "62440e322008e87fb29c1fd0",
    "channel_properties": {
        "cards": {
            "skip_three_ds": true
        }
    },
    "cards_session_js": {
        "success_return_url": "https://yourcompany.com/success",
        "failure_return_url": "https://yourcompany.com/failure"
    }
}
  1. Gather the card information

    Initiate card_session.js on your page. Collect the card information using Xendit.payment.collectCardData(reqData, xenditResponseHandler). This will collect the card information and send it to Xendit using card_session.js.

Request - to card_session_js

{
    "card_number": "4000000000001091",
    "expiry_month": "12",
    "expiry_year": "2040",
    "cvn": "123",
    "cardholder_first_name": "firstName",
    "cardholder_last_name": "lastName",
    "cardholder_email": "shopper@emailaddress.com",
    "cardholder_phone_number": "+61111111111",
    "save_card_payment_token": true,
    "payment_session_id": "YOUR_PAYMENT_SESSION_ID"
}

Response - from card_session_js

{ 
    "message": "Status updated. Wait for a callback or get the status using the Get API.", 
    "payment_request_id": "PAYMENT_REQUEST_ID" 
}

Important: Store the payment_request_id as it's crucial for retrieving the transaction's status later.

  1. Receive the webhook

    Xendit sends a payment webhook to your webhook endpoint indicating the final state of the transaction. You can match the payment webhook with the payment_request_id.

Example payment.capture webhook:

{
    "created": "2024-12-18T05:46:35.109Z",
    "business_id": "62440e322008e87fb29c1fd0",
    "event": "payment.capture",
    "data": {
        "type": "PAY",
        "status": "SUCCEEDED",
        "country": "ID",
        "created": "2024-12-18T05:46:08.192Z",
        "updated": "2024-12-18T05:46:30.627Z",
        "captures": [
            {
                "capture_id": "cptr-08f17fa3-e80c-4d8e-8c34-17aa3400bc1c",
                "capture_amount": 10000,
                "capture_timestamp": "2024-12-18T05:46:34.234Z"
            }
        ],
        "currency": "IDR",
        "payment_id": "py-3f57d678-2448-4c9f-a433-8468d366fb5c",
        "business_id": "62440e322008e87fb29c1fd0",
        "customer_id": "cust-7de9a9b4-37e8-40ad-b665-d97f42e538c5",
        "channel_code": "CARDS",
        "reference_id": "97ba0a32-b996-4abf-8a7b-6184a6644676_b8d18f2f-3",
        "capture_method": "AUTOMATIC",
        "request_amount": 10000,
        "payment_details": {
            "authorization_data": {
                "reconciliation_id": "7345007929096981703954",
                "authorization_code": "831000",
                "acquirer_merchant_id": "xendit_ctv_agg",
                "network_response_code": "00",
                "network_transaction_id": "016153570198200",
                "cvn_verification_result": "M",
                "retrieval_reference_number": "435205253972",
                "address_verification_result": "M",
                "network_response_code_descriptor": "Approved and completed sucessfully"
            },
            "authentication_data": {
                "flow": "CHALLENGE",
                "a_res": {
                    "eci": "05",
                    "message_version": "2.1.0",
                    "authentication_value": "AAIBBYNoEwAAACcKhAJkdQAAAAA=","directory_server_trans_id": "e537f539-d59f-4ebe-8d56-7fdc31a8e9b4"
                }
            }
        },
        "payment_request_id": "pr-5593127f-8c7b-4d2f-b487-c785ffc21e2f",
        "payment_token_id": "pt-8983127f-98er-76dr-67er-d985gff21z2f"
    },
    "api_version": "v3"
}

It's recommended to save the payment_id and payment_details from the webhook, correlated with the payment_request_id, as proof of payment.

Get payment token details

To retrieve details of a saved payment token, such as a masked_card_number, use the following endpoint:

https://api.xendit.co/v3/payment_token/YOUR_PAYMENT_TOKEN_ID

This will return the details of the payment token.

Response - from payment_token

{
    "payment_token_id": "pt-f5f570a2-f204-49ae-b2f0-10b786de5047",
    "business_id": "62440e322008e87fb29c1fd0",
    "customer_id": "cust-c1a6b0a0-a501-4479-8b57-d038601f139e",
    "country": "ID",
    "reference_id": "ref-id-1_Z6a2sG4sfp",
    "currency": "IDR",
    "status": "ACTIVE",
    "actions": [],
    "created": "2025-01-10T08:45:24.687Z",
    "updated": "2025-01-10T08:45:41.246Z",
    "channel_properties": {
        "success_return_url": "https://checkout-staging.xendit.co/session/ps-6780de086602873447b03b01?token_request_id=82232fe7-32bc-4004-ae15-0cb99dc56b5f",
        "failure_return_url": "https://checkout-staging.xendit.co/session/ps-6780de086602873447b03b01?token_request_id=82232fe7-32bc-4004-ae15-0cb99dc56b5f",
        "skip_three_ds": false,
        "card_on_file_type": "RECURRING",
        "recurring_configuration": {
            "recurring_expiry": "2025-12-12",
            "recurring_frequency": 30
        },
        "card_details": {
            "masked_card_number": "400000XXXXXX1091",
            "expiry_month": "01",
            "expiry_year": "2028",
            "fingerprint": "62c571cc9f1788001b255135",
            "type": "CREDIT",
            "network": "VISA",
            "country": "ID",
            "issuer": "BRI",
            "cardholder_first_name": "John",
            "cardholder_last_name": "Doe",
            "cardholder_email": "test@yourdomain.com",
            "cardholder_phone_number": "+628121233333"
        }
    },
    "channel_code": "CARDS",
    "token_details": {
        "authentication_data": {
            "flow": "CHALLENGE",
            "a_res": {
                "eci": "05",
                "message_version": "2.1.0",
                "authentication_value": "AAIBBYNoEwAAACcKhAJkdQAAAAA=","directory_server_trans_id": "df613bfd-2619-4f1c-989c-f7b32029d4a7"
            }
        },
        "authorization_data": {
            "cvn_verification_result": "M",
            "address_verification_result": "M",
            "network_response_code": "00",
            "network_response_code_descriptor": "Approved and completed sucessfully",
            "authorization_code": "831000",
            "retrieval_reference_number": "501008635675",
            "acquirer_merchant_id": "xendit_ctv_agg",
            "reconciliation_id": "7364987404086454403955",
            "network_transaction_id": "016153570198200"
        }
    }
}

Use payment token for future transactions

Once a payment token is generated, it acts as a secure reference to the end user's stored card details. You can use this token for future transactions, based on your agreement with the end user.

Common Use Cases for Payment Tokens:

  1. Customer-Initiated Transactions (Returning Customers) - Enable returning customers to quickly select a saved card at checkout. To improve the success rate, we recommend prompting users to re-enter their CVN when completing the transaction. See our integration guide

  2. Merchant-Initiated Transactions (Recurring Payments) - Automate recurring billing (subscriptions, etc.) without customer interaction. See our integration guide