Integration guides
Working with the API
Open in Markdown

Pre-built forms (popup or embedded)

Embed our secure, pre-built payment forms directly into your existing checkout page. Perfect for customizing your user flow while we handle PCI compliance.

Account setup and configuration

Before you can integrate, you must set up your merchant environment. You’ll need the following:

  • Create a merchant account: Sign up and gain access to your merchant dashboard

  • Create an SDK installation: In your dashboard, under developer settings, create a new SDK installation for each website or mobile application you plan to deploy on. This process involves:

    • Choosing your platform (e.g., Web, iOS, Android).
    • Providing application details like your website domain or app bundle ID.
    • Selecting payment methods you want to offer on that platform (e.g., Card, PromptPay).
    • Configuring customer data to require (e.g., email address, mobile number).
  • Create a new API key: In your dashboard, under developer settings, create a new API key. Keep your API key secret. Your API key is a secret credential. It must only be used on your backend server. Never expose this key in your frontend (client-side) JavaScript, mobile app code, or any public repository.

  • Connect a webhook endpoint (optional): In your dashboard, under developer settings, create a new webhook endpoint and listen to the payment_complete event.

Integration flow overview

A complete transaction involves both your frontend (handling user interaction) and your backend (handling secure API calls).

  1. On the frontend (aka client-side): Your customer interacts with Reservepay pre-built form which captures all necessary payment details and creates a payment_session_id via the onPaymentSessionReady callback.

  2. On the backend (aka server-side): Your frontend sends the payment_session_id to your server. Your server then securely uses its secret API key to call the Reservepay Merchant API and initiate the actual payment flow.

  3. Back on the frontend: The Reservepay pre-built form automatically displays the payment status and hands back control to you via the callbacks.

Testing card payments: Use the test card generator to create Luhn-compliant test card numbers to complete card payments.

Web integration

Install the SDK

Include the SDK script tag in your HTML file. This loads the reservepay object into your application.

To maintain strict PCI compliance and ensure the integrity of sensitive payment information, our library must be loaded directly from our secure servers via a script tag and must not be bundled or imported via a package manager.

<script src="https://sdk.reservepay.com/js/v1/reservepay.js"></script>

Attach the SDK

Once the SDK script is loaded, you can initialize the payment form using the window.Reservepay.initReservepayFormUI method. This function takes a configuration object.

The required top-level parameters are:

  • formType: Defines the UI mode. Use "popup" or "embedded".
  • merchantId: Your unique merchant identifier.
  • installationId: The specific installation ID for this integration.
  • formOptions: An object containing configuration specific to the form, such as amount and the necessary event callbacks.

The popup opens when the customer selects the trigger element.

<button id="reservepay-popup-trigger">Pay Now</button>
window.Reservepay.initReservepayFormUI({
  formType: "popup",
  merchantId: "123456789100",
  installationId: "ins_123",
  formOptions: {
    popupTriggerSelector: "#reservepay-popup-trigger",
    amount: "100000",
    async onPaymentSessionReady(sessionId) {
      await sendPaymentSessionToBackend(sessionId)
    },
    onPaymentSuccess(sessionId) {
      console.log("Payment successful", sessionId)
    },
    onPaymentFailed(sessionId) {
      console.log("Payment failed", sessionId)
    },
  },
})

Embedded form

The embedded form renders inside a container in your checkout.

<div id="reservepay-mount"></div>
window.Reservepay.initReservepayFormUI({
  formType: "embedded",
  merchantId: "123456789100",
  installationId: "ins_123",
  formOptions: {
    containerSelector: "#reservepay-mount",
    amount: "100000",
    async onPaymentSessionReady(sessionId) {
      await sendPaymentSessionToBackend(sessionId)
    },
    onPaymentSuccess(sessionId) {
      console.log("Payment successful", sessionId)
    },
    onPaymentFailed(sessionId) {
      console.log("Payment failed", sessionId)
    },
  },
})

sendPaymentSessionToBackend is a placeholder for your frontend-to-backend request. Implement it to send the sessionId to your server, then follow Initiate the payment flow.

Handle callbacks

Callback When it runs What to do
onPaymentSessionReady(sessionId) The SDK creates the payment session, before the customer submits payment details. Send the sessionId to your backend and associate it with the order or cart.
onPaymentSuccess(sessionId) The customer completes the payment flow. Show a success state or redirect to a confirmation page.
onPaymentFailed(sessionId) The payment fails, is declined, or is canceled. Show the failure and let the customer retry when appropriate.

Use frontend callbacks to update the UI. Fulfill the order only after a webhooks or backend status check confirms the payment.

Accept payments with a saved card

Saved cards let returning customers check out with a single CVV entry: collect a reusable token on the first payment, then pass it back to the form on later visits.

Saving a card

Set enableSaveCard: true in formOptions. The SDK shows a Save this card checkbox; when the customer opts in, onPaymentSessionReady receives a second argument: { saveCard: true, savedCardToken }. Send both to your backend and associate the card with the customer — inline when you initiate the payment flow, or via save-card. After ReservePay saves the card, you can retrieve its opaque token instead of storing it separately.

The SDK acknowledges the session even if onPaymentSessionReady throws, so the payment continues. Handle token-storage failures on your backend.

window.Reservepay.initReservepayFormUI({
  formType: "embedded",
  merchantId: "123456789100",
  installationId: "ins_123",
  formOptions: {
    containerSelector: "#reservepay-mount",
    amount: "100000",
    enableSaveCard: true,
    async onPaymentSessionReady(sessionId, details) {
      await sendPaymentSessionToBackend(sessionId)
      if (details.saveCard) {
        await saveCardForCustomer(details.savedCardToken) // your backend call
      }
    },
    onPaymentSuccess(sessionId) {
      console.log("Payment successful", sessionId)
    },
    onPaymentFailed(sessionId) {
      console.log("Payment failed", sessionId)
    },
  },
})

Reusing a saved card

On later checkouts, retrieve the customer's saved-card tokens from your backend, then pass them through formOptions.savedCardTokens (string or array). The form displays the masked card and asks only for the CVV — with multiple tokens, it shows a card picker and a Use a different card option first.

If a token in savedCardTokens cannot be decoded — for example, if it is malformed or stale — it's dropped silently. If none decode, the SDK falls back to the standard new-card form automatically.

window.Reservepay.initReservepayFormUI({
  formType: "embedded",
  merchantId: "123456789100",
  installationId: "ins_123",
  formOptions: {
    containerSelector: "#reservepay-mount",
    amount: "100000",
    savedCardTokens: customerSavedCardTokens, // string or string[] from your backend
    async onPaymentSessionReady(sessionId) {
      await sendPaymentSessionToBackend(sessionId)
    },
    onPaymentSuccess(sessionId) {
      console.log("Payment successful", sessionId)
    },
    onPaymentFailed(sessionId) {
      console.log("Payment failed", sessionId)
    },
  },
})

Android integration

Install the SDK

Add the SDK to your app's build.gradle:

repositories {
    mavenCentral()
}

dependencies {
    implementation 'com.reservepay:reservepay-android:1.0.0'
}

Declare ReservepayActivity in AndroidManifest.xml:

<activity
    android:name="com.reservepay.ReservepayActivity"
    android:exported="false" />

Start a payment

Configure the SDK once, typically in Application.onCreate(), then call startPayment for each transaction.

import com.reservepay.ReservepayCallbacks
import com.reservepay.ReservepaySDK
import com.reservepay.configure
import com.reservepay.startPayment

// Create callback
private val reservepayCallbacks = object : ReservepayCallbacks {
    override fun onPaymentSessionReady(sessionId: String, savedCardToken: String?) {
        // Send sessionId to your backend. Store savedCardToken when present.
    }
    override fun onPaymentStatus(status: String) {
    }
    override fun onPaymentCancelled() {
    }
    override fun onPaymentError(error: String) {
    }
}

// Configure the SDK once, before starting any payment
ReservepaySDK.getInstance().configure(
    context = context,
    isDebug = true,
    merchantId = "123456789100",
    installationId = "ins_123",
)

// Start a payment
ReservepaySDK.getInstance().startPayment(
    context,
    100000L, // THB 1,000.00 in satang
    "THB", // Currency
    tokens = customerSavedCardTokens, // Saved card tokens to offer for reuse, or null for none
    callback = reservepayCallbacks
)

Mobile amounts are integers in the currency's smallest unit. Pass saved-card tokens through tokens, or null when the customer has none. See Mobile callback behavior before continuing to Backend integration.

iOS integration

Install the SDK

In Xcode, choose File > Add Package Dependencies. Enter the Reservepay package URL supplied with your SDK release, select that release's supported version, and add ReservepaySDK to your target.

Start a payment

Configure the SDK once at app launch, then call startPayment for each transaction.

import ReservepaySDK

// Create callback
let callback = ReservepayCallback(
    onPaymentSessionReady: { sessionId, savedCardToken in
        // Send sessionId to your backend. Store savedCardToken when present.
    },
    onPaymentCancelled: {
        print("Payment cancelled")
        // Dismiss the view controller
        viewController.dismiss(animated: true)
    },
    onPaymentStatus: { status in
        print("Payment status: \(status)")
    },
    onPaymentError: { error in
        print("Payment error: \(error)")
    }
)

// Configure the SDK once, before starting any payment
ReservepaySDK.companion.getInstance().configure(
    isDebug: true,
    merchantId: "123456789100",
    installationId: "ins_123"
)

// Start a payment
ReservepaySDK.companion.getInstance().startPayment(
    viewController: viewController,
    amount: 100000, // THB 1,000.00 in satang
    currency: "THB",
    tokens: customerSavedCardTokens, // Saved card tokens to offer for reuse, or nil for none
    callback: callback
)

Mobile amounts are integers in the currency's smallest unit. Pass saved-card tokens through tokens, or nil when the customer has none.

Mobile callback behavior

Android and iOS use the same callback roles:

Callback What to do
onPaymentSessionReady Send the session ID to your backend. If the saved-card token is present, store it against the customer.
onPaymentStatus Update the UI from the reported status.
onPaymentError Show or log the error.
onPaymentCancelled Dismiss the payment flow or return to checkout.

Web forms report completion through onPaymentSuccess and onPaymentFailed; mobile uses the status, error, and cancellation callbacks above. Continue with the shared backend steps after onPaymentSessionReady.

Backend integration

These steps are identical regardless of which platform initiated the payment session.

Initiate the payment flow

After your frontend sends the payment_session_id, your backend must call the initiate-payment-flow endpoint which will return a payment_id. This call must be authenticated using your secret API key as a bearer token in the Authorization header. This "authorizes" the payment session and kicks off the payment flow.

Example

// IMPORTANT: This must be called from your server-side code with your API key
// Never expose your API key (reservepay_xxx) in client-side code
// Make a request from your backend to Reservepay API:
//
await fetch('https://api.reservepay.com/merchants/initiate-payment-flow', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY', // Use your API key here (server-side only!)
    'Api-Version': '2025-04-01',
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    amount: "1000",
    currency: "THB",
    payment_session_id: paymentSessionId,
    capture: true,
    return_url: "https://your-site.com/return-url"
  })
})

Saving and reusing cards

If the customer consented to saving their card, you can persist it two ways. Inline with this payment: add customer_id and save_card to the initiate-payment-flow call you already make (below). On its own: use the standalone save-card endpoint when no payment is in flight — see Saving cards for future use.

Save a card

Add customer_id and save_card to your initiate-payment-flow call. This only saves the card if the reusable token from the steps above is multi-use. Set save_card to ADD to attach the card to the customer, or DEFAULT to also make it their default card.

await fetch('https://api.reservepay.com/merchants/initiate-payment-flow', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Api-Version': '2025-04-01',
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    amount: "1000",
    currency: "THB",
    payment_session_id: paymentSessionId,
    capture: true,
    return_url: "https://your-site.com/return-url",
    customer_id: customerId,
    save_card: "ADD"
  })
})

If saving the card fails, the payment still goes through. The error is not surfaced in the response — check your saved cards afterward if you need to confirm.

Reuse a saved card

To reuse a saved card, you don't need to store the original token yourself. Look up the customer's cards with retrieve-contact. Each active card includes the merchant-scoped opaque token accepted by the browser SDK, while card_id identifies the card for backend operations.

const contact = await fetch('https://api.reservepay.com/merchants/retrieve-contact', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Api-Version': '2025-04-01',
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ contact_id: customerId })
}).then(res => res.json())

const savedCards = contact.default_card
  ? [
      contact.default_card,
      ...contact.cards.filter(
        card => card.card_id !== contact.default_card.card_id
      )
    ]
  : contact.cards

const savedCardTokens = savedCards.map(card => card.token)

Return savedCardTokens from your backend and pass it to the browser SDK for a customer-present saved-card checkout. The example puts the default card first because the SDK initially selects the first valid token.

For a merchant-initiated payment instead, pass a card's card_id to request-payment to charge it directly from your backend. This doesn't need a payment session, SDK involvement, or the customer present, and it skips 3D Secure. Omit card_id to charge the customer's default card instead.

await fetch('https://api.reservepay.com/merchants/request-payment', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Api-Version': '2025-04-01',
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    amount: "1000",
    currency: "THB",
    installation_id: installationId,
    capture: true,
    customer_id: customerId,
    card_id: cardId
  })
})

request-payment requires the card payment method's MIT (merchant-initiated transaction) flag. Contact support to enable it before using this endpoint.

To look up a single card by its ID or token instead of listing all of a customer's cards, use find-card.

Confirming the payment status

To confirm the payment status from your backend call find-payment using the payment_id returned by the initiate-payment-flow endpoint. This returns the full payment object, including the status field.

Webhooks (optional)

For a more robust integration, we highly recommend using webhooks. Instead of relying only on polling, your server can receive asynchronous notifications. To achieve the same result as the previous step, listen for the payment_completed event.

To verify the signature of incoming events our requests include an Reservepay-Signature header. You must verify this signature using your webhook endpoint verify key (available in your dashboard) and a libsodium compatible library. This verification is critical to make sure that the request genuinely came from Reservepay and was not forged. See Real-time notifications for setup and verification instructions.

Accept payments in websites and apps

Choose the integration method that best fits your needs and start accepting payments seamlessly on the web and in mobile apps.

Hosted checkout

Redirect customers to our secure, optimized checkout page. Perfect for quick integration with minimal development effort.

Read the guide
Pre-built forms

Embed our secure, pre-built payment forms directly into your existing checkout page while we handle PCI compliance.

Read the guide
Custom UI

Build your entire checkout experience from the ground up using our powerful APIs and SDK for complete control.

Read the guide

No account yet?

Start integrating all these amazing features into your app or website by creating your own merchant account today. It's free to sign up and only takes a few minutes to get started.