Saved Card Payments

Direct Payments & Saved Cards (Embedded) allow users to complete payments directly within your app or website without redirection, ensuring a seamless checkout experience. It also enables secure storage of card details via tokenization, allowing faster and convenient repeat transactions.

💳

Saved Cards

Zero-touch charging after a single 3DS verification
① VERIFY
Save a card with full 3DS authentication. No charge — just a stored-credential token for future use.
② Charge Card
Charge a previously saved card server-to-server, any time, with no customer interaction required.

API Endpoints

All merchant-authenticated endpoints share the same wire format: encrypted data payload + accessCode header.

Sandbox Base URL

Below Checkout API strictly for sandbox testing only. Never use these sandbox URLs in production.

https://sandbox.hesabe.com

Production Base URL

Use the following Checkout API strictly for production transactions.

https://api.hesabe.com
Method & PathPurpose
POST /api/direct-payment/customerCreate the customer the saved card will belong to
POST /checkoutFor both first-time checkout and verifying new card details before saving
POST /api/direct-payment/charge-cardCharge a previously saved card_id server-to-server
GET /api/direct-payment/cardsList a customer's reusable saved cards
DELETE /api/direct-payment/cards/{cardId}Remove a saved card (Deletes the MPGS token too)

Prerequisites

  • Direct Payment (MPGS) must be enabled on your Hesabe account.

Enable Direct Payment

Contact Hesabe support to enable the Direct Payment (MPGS) feature on your merchant account.

Contact Hesabe Support Team:support@hesabe.com

Implementation Steps

Web Checkout Form

Refere API Details

Card Payments & Saved Cards

You can follow the API Integration Setup guide to get started.

Create a Customer

If the customer does not already have a Hesabe Customer ID, create one first.

Sandbox URL

Below Checkout API strictly for sandbox testing only. Never use these sandbox URLs in production.

https://sandbox.hesabe.com

Production URL

Use the following Checkout API strictly for production transactions.

https://api.hesabe.com
POST /api/direct-payment/customer
Request body
{
  "name": "Ahmed Al-Mansouri",
  "email": "ahmed@example.com",
  "mobile_number": "+96512345678",
  "customer_id": "YOUR_INTERNAL_USER_ID"
}

Important Note

Store the returned customer_id from the response — you will pass it when creating the checkout session.

For first time checkout

Web Checkout Form

Pre-requisites Setup

You can follow the Checkout Setup guide to get started.

Hesabe Request Handler

You can follow the Hesabe Request Handler guide to get started.

Sandbox Checkout API

Below Checkout API strictly for sandbox testing only. Never use these sandbox URLs in production.

https://sandbox.hesabe.com/checkout

Production Checkout API

Use the following Checkout API strictly for production transactions.

https://api.hesabe.com/checkout

Encrypt the following JSON payload with your merchant key and send it as the data parameter:

Checkout payload (before encryption)
{
  "merchantCode": "YOUR_MERCHANT_CODE",
  "amount": "20.000",  
  "currency": "KWD",
  "saveCard": true,  // Pass `true` to display a checkbox in the form, allowing the user to save the card
  "customer_id": 42, // Provide the `customer_id` if you want to save the card details.
  "responseUrl": "https://sandbox.hesabe.com/customer-response?id=842217", // Change to your response URL 
  "failureUrl": "https://sandbox.hesabe.com/customer-response?id=842217", // Change to your failure URL 
  "variable1": "signup_flow",
  "version": "3.0", 
  "channel": "mobile"
}

Key fields

FieldTypeRequiredDescription
amount
NumericYesActual Checkout amount must be more than zero
saveCard
BooleanYesMust be true if you want to show checkbox to save a card if user comes first time
customer_id
IntegerYesHesabe Customer ID. The saved card is attached to this customer
currency
StringYesISO currency code (e.g. KWD)
responseUrl
URLYesRedirect URL after successful verification
failureUrl
URLYesRedirect URL if verification or 3DS fails
variable1–5
AlphanumericNoCustom parameters returned as-is in the callback
version
NumericYesMust be 3.0
channel
StringYesMust be mobile

The response will include a ready-to-use webviewUrl:

// Sandbox response

{
  "status": true,
  "response": {
    "data": "<encrypted_payload>", 
    "webviewUrl": "https://sandbox.hesabe.com/pay/webview?data=<encrypted_payload>"
  }
}

To get transaction results

Listen your navigation state change (responseUrl / failureUrl) and get the decrypted result data from that URL

const onNavigationStateChange = (navState) => {
  const url = navState.url;
  if (url && url.includes('https://sandbox.hesabe.com/customer-response?id=842217')) {
    closeWebView();  // close the web view page
    const parts = url.split('data='); // get the decrypted result data form the `responseUrl` or `failureUrl`
    if (parts[1]) {
      navigation.navigate('TransactionResult', {
        resultdata: parts[1]. // Navigate to result page with transaction data
      });
    }
  }
};

To save new card details / Initiate a VERIFY session

Web Checkout Form

Call the standard Hesabe Checkout endpoint with amount = 0.000 and saveCard = true. The customer’s card will be authenticated and securely saved, and no charges will be made.

Sandbox Checkout API

Below Checkout API strictly for sandbox testing only. Never use these sandbox URLs in production.

https://sandbox.hesabe.com/checkout

Production Checkout API

Use the following Checkout API strictly for production transactions.

https://api.hesabe.com/checkout

Encrypt the following JSON payload with your merchant key and send it as the data parameter:

Checkout payload (before encryption)
{
  "merchantCode": "YOUR_MERCHANT_CODE",
  "amount": "0.000",  // Must be "0.000 to verify the card"
  "currency": "KWD",
  "saveCard": true,  //  Pass `true` to display a checkbox in the form, allowing the user to save the card
  "customer_id": 42, // Provide the `customer_id` if you want to save the card details.
  "responseUrl": "https://yoursite.com/payment/callback",
  "failureUrl": "https://yoursite.com/payment/failed",
  "variable1": "signup_flow",
  "version": "3.0", 
  "channel": "mobile"
}

Key fields

FieldTypeRequiredDescription
amount
NumericYesMust be exactly 0.000 — triggers the VERIFY flow
saveCard
BooleanYesMust be true together with amount=0
customer_id
IntegerYesHesabe Customer ID. The saved card is attached to this customer
currency
StringYesISO currency code (e.g. KWD)
responseUrl
URLYesRedirect URL after successful verification
failureUrl
URLYesRedirect URL if verification or 3DS fails
variable1–5
AlphanumericNoCustom parameters returned as-is in the callback
version
NumericYesMust be 3.0
channel
StringYesMust be mobile

What the customer sees

The hosted payment form shows a "Save Card" button instead of the usual "Pay X KWD". The amount line is hidden and the saved-card list is suppressed — the customer always enters a new card.

Behind the scenes: Hesabe uses MPGS non-payment authentication (purpose = ADD_CARD) so the issuer knows no funds will be moved. You don't need to do anything special.


// Sandbox response

{
  "status": true,
  "response": {
    "data": "<encrypted_payload>", 
    "webviewUrl": "https://sandbox.hesabe.com/pay/webview?data=<encrypted_payload>"
  }
}

To get result data for VERIFIED session

Follow the same code structure from Step 4 to handle navigation state changes and extract the result data from the WebView URL.

Handle the VERIFY Callback

After the customer completes (or abandons) the VERIFY flow, Hesabe redirects to your responseUrl (success) or failureUrl (failure) with an encrypted data query parameter.

https://yoursite.com/payment/callback?data=ENCRYPTED_PAYLOAD

Decrypt data using your merchant key and IV.

Decrypted response
{
  "status": true,
  "code": 1,
  "message": "Card Verified",
  "response": {
    "resultCode": "VERIFIED",
    "amount": "0.000",
    "currency": "KWD",
    "paymentToken": "MERCH20250426abc123",
    "paymentId": null,
    "paidOn": "2025-04-26 14:32:01",
    "orderReferenceNumber": "signup_flow",
    "trackID": 1270310,
    "transactionId": "MERCH20250426abc123_PAY",
    "variable1": "signup_flow",
    "customer": {
      "Name": "Ahmed Al-Mansouri",
      "Email": "ahmed@example.com",
      "Mobile": "+96512345678",
      "CardNumber": "512345xxxxxx0008",
      "CardType": "MASTERCARD"
    },
    "cardId": 77
  }
}
Decrypted response
{
  "status": false,
  "code": 0,
  "message": "Verification Failed",
  "response": {
    "resultCode": null,
    "amount": "0.000",
    "cardId": null
  }
}

Store the 'cardId' information

response.cardId is the Hesabe Card ID for the saved card.

Store this value - you will use it to charge the customer later.

Response fields

FieldDescription
statustrue = card saved successfully, false = failed
cardIdID of the newly saved card. null on failure
paymentTokenToken for this VERIFY transaction
trackIDHesabe internal track ID

Iframe SDK Mode (alternative)

If you are embedding the Hesabe payment iframe via the JavaScript SDK, the verify result is delivered as a payment-success / payment-error event — not as a redirect.

Success Response
{
  "status": true,
  "message": "Card Verified",
  "response": {
    "redirect_url": "https://hesabe.com/checkout-response/...",
    "data": {
      "amount": "0.000",
      "paymentToken": "MERCH20250426abc123",
      "resultCode": "VERIFIED",
      "currency": "KWD",
      "paidOn": "2025-04-26 14:32:01",
      "encryptedData": "BASE64_ENCRYPTED_PAYLOAD"
    }
  }
}
FieldDescription
data.resultCode"VERIFIED" for verify-only orders (vs "CAPTURED" for paid orders)
data.encryptedDataEncrypted blob containing cardId, full customer info, masked card number, etc.
data.redirect_urlThe URL the redirect-mode flow would have sent the customer to

cardId is not in plaintext in iframe mode

cardId is intentionally omitted from the top-level iframe response. Decrypt encryptedData on your backend (same algorithm as redirect-mode) to extract cardId and card metadata. Never decrypt it in the browser.

After decryption, the JSON matches the redirect-mode success response — same cardId, customer block, and paymentToken.

Charge the Saved Card

Web Checkout Form

Once you have a cardId, charge the card at any time from your backend — no customer interaction required.

Server-side only

Never call this endpoint from a browser or mobile client. Your accessCode must stay private.

Sandbox Base URL

Below Checkout API strictly for sandbox testing only. Never use these sandbox URLs in production.

https://sandbox.hesabe.com

Production URL

Use the following Checkout API strictly for production transactions.

https://api.hesabe.com

Endpoint

POST /api/direct-payment/charge-card

Authentication headers

HeaderValue
accessCodeYour merchant UUID (36 characters)
Content-Typeapplication/json

Request payload (before encryption)

Charge card payload
{
  "merchantCode": "YOUR_MERCHANT_CODE",
  "card_id": 77, 
  "customer_id": 42, 
  "amount": "9.900",
  "order_reference": "INVOICE-2025-001"
}
FieldTypeRequiredDescription
merchantCode
StringYesYour Hesabe merchant code
card_id
IntegerYesThe cardId returned from VERIFY
customer_id
IntegerNoWhen provided, the request is rejected if the card does not belong to this customer
amount
NumericYesAmount in KWD (must be > 0)
order_reference
StringNoYour internal order / invoice ID

Encrypted request example

HTTP request
POST /api/direct-payment/charge-card
accessCode: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Content-Type: application/json

{
  "data": "ENCRYPTED_PAYLOAD"
}
{
  "status": true,
  "code": 200,
  "message": "Payment successful",
  "response": {
    "resultCode": "CAPTURED",
    "amount": "9.900",
    "currency": "KWD",
    "paymentToken": "MERCH20250426xyz789",
    "paymentId": "80123456",
    "paidOn": "2025-04-26 14:32:01",
    "orderReferenceNumber": "INVOICE-2025-001",
    "auth": "123456",
    "trackID": 1270311,
    "transactionId": "MERCH20250426xyz789_PAY",
    "customer": {
      "Name": "Ahmed Al-Mansouri",
      "Email": "ahmed@example.com",
      "Mobile": "+96512345678",
      "NameOnCard": "AHMED AL-MANSOURI",
      "CardNumber": "512345xxxxxx0008",
      "CardType": "MASTERCARD"
    },
    "cardId": 77
  }
}
{
  "status": false,
  "code": 0,
  "message": "Insufficient funds",
  "response": {
    "resultCode": "DECLINED",
    "amount": "9.900"
  }
}

Response fields

FieldDescription
statustrue = charged successfully
response.resultCodeCAPTURED on success, DECLINED or similar on failure
response.amountAmount charged in KWD
response.paymentTokenHesabe token for this charge transaction
response.transactionIdMPGS transaction ID
response.paymentIdBank receipt / reference number
response.cardIdThe card_id that was charged
response.customerCustomer name, email, mobile, and masked card info

Manage Saved Cards

Web Checkout Form

Two helper endpoints let you list and remove a customer's saved cards from your backend. Both use the same encrypted data payload + accessCode header authentication as Charge Card.

List a customer's saved cards

Sandbox URL

Below Checkout API strictly for sandbox testing only. Never use these sandbox URLs in production.

https://sandbox.hesabe.com

Production URL

Use the following Checkout API strictly for production transactions.

https://api.hesabe.com
GET /api/direct-payment/cards?data=ENCRYPTED_PAYLOAD

Payload (before encryption)

{
  "merchantCode": "YOUR_MERCHANT_CODE",
  "customer_id": 42
}
FieldTypeRequiredDescription
merchantCode
StringYesYour Hesabe merchant code
customer_id
IntegerYesHesabe Customer ID

Only cards usable in Charge Card are returned: MPGS-only, status = active, with a stored token, 3DS metadata, and not yet expired.

Success response

{
  "status": true,
  "code": 200,
  "message": "Customer cards fetched successfully",
  "response": {
    "cards": [
      {
        "card_id": 77,
        "card_number": "****0008",
        "expiry_month": "01",
        "expiry_year": "39",
        "brand": "MASTERCARD",
        "name_on_card": "AHMED AL-MANSOURI"
      },
      {
        "card_id": 92,
        "card_number": "****4242",
        "expiry_month": "06",
        "expiry_year": "28",
        "brand": "VISA",
        "name_on_card": "AHMED AL-MANSOURI"
      }
    ]
  }
}

card_id is the same integer returned by VERIFY and accepted by Charge Card.

If the customer doesn't belong to your merchant, the customer is inactive, or customer_id is missing / invalid, the response is Invalid Request Data with HTTP 400.


Remove a saved card

Web Checkout Form

Sandbox URL

Below Checkout API strictly for sandbox testing only. Never use these sandbox URLs in production.

https://sandbox.hesabe.com

Production URL

Use the following Checkout API strictly for production transactions.

https://api.hesabe.com
DELETE /api/direct-payment/cards/{cardId}

Body (before encryption)

{
  "merchantCode": "YOUR_MERCHANT_CODE"
}
LocationFieldTypeRequiredDescription
URLcardIdIntegerYesThe card_id from VERIFY or GET /cards
Body (encrypted)merchantCodeStringYesYour Hesabe merchant code

Encrypted request example

HTTP request
DELETE /api/direct-payment/cards/77
accessCode: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Content-Type: application/json

{
  "data": "ENCRYPTED_PAYLOAD"
}
{
  "status": true,
  "code": 200,
  "message": "Card removed successfully",
  "response": null
}
{
  "status": false,
  "code": 0,
  "message": "Invalid Request Data",
  "response": null
}

The token is deleted at MPGS first. Only if MPGS confirms deletion is the card deactivated on Hesabe's side. Once removed, any chargeCard call with that card_id will fail with Invalid Request Data.


Error Codes

HTTP StatusMeaning
200Request processed — check status field for payment outcome
400Invalid request — missing fields, amount ≤ 0, or card not found
401Authentication failed — invalid accessCode or merchantCode mismatch
422Card not eligible for merchant-initiated charge — card was saved before the VERIFY flow was introduced and lacks required 3DS stored-credential metadata. Re-save via a new VERIFY session
429Rate limit exceeded (30 requests / minute)
500Internal error — contact support with the error details

Common message values on failure

MessageCause
Invalid Request Datacard_id not found, card inactive, or card does not belong to a customer of this merchant
Card is not eligible for merchant-initiated charge. Please re-save the card.Card is missing the stored-credential agreement ID — re-run VERIFY
Insufficient fundsCard declined by issuer
Do not honourCard issuer blocked the transaction
Invalid merchant data providedWrong accessCode or merchantCode

Security Notes

Keep card_id values private

card_id values are integers but should be treated as opaque references. Do not expose them to the end customer.

  • Charge Card is server-side only — never call it from a browser or mobile client. Your accessCode must stay private.
  • Each card_id is scoped to the customer it was saved for. Attempting to charge a card that does not belong to one of your customers returns Invalid Request Data.
  • VERIFY stores a 3DS-authenticated stored credential. Charge Card transactions qualify as merchant-initiated transactions under card scheme rules and do not require another 3DS challenge.
  • Card data never touches your server. All card input is handled inside Hesabe's hosted form or iframe.

FAQ


Support

For integration support or to enable Direct Payment on your account:

Hesabe Support

Contact Hesabe Support Team:support@hesabe.com
🚀

Ready to Launch

Enable zero-touch recurring payments for your platform
🔐3DS Authenticated
Every saved card goes through full 3D Secure authentication — qualifying charges as merchant-initiated transactions.
Instant Server Charges
Charge any saved card from your backend in a single API call — no customer interaction, no redirects.
🔄Reusable Tokens
A single card_id can be charged multiple times — perfect for subscriptions, invoices, and pay-as-you-go billing.

By implementing Card on File, you give customers a seamless one-time setup and your platform the ability to charge on demand — increasing retention and reducing payment friction.