# Add funds with a card

## Introduction

Glomo Global Add Funds enables banks and financial institutions to offer their customers instant account funding from an overseas Visa debit card, directly within their own application. Instead of initiating an international wire and waiting days for funds to arrive, the customer completes a card payment in a single session and their account is credited immediately.

The offering includes SDKs and APIs that let partner institutions integrate the funding journey into their existing add-funds flow. Glomo handles card acceptance, foreign exchange, screening and compliance, and settlement - the partner retains full ownership of the customer relationship and the user experience.

## How it works

The customer initiates an add-funds request in the partner's application and enters the amount they wish to add. Glomo returns a quote showing the exchange rate and total cost in the customer's own currency. On confirmation, the partner creates a Glomo customer - passing across the KYC records it already holds for that customer - registers the account the funds will land in, and then creates an order. The Glomo SDK opens within the partner's application to capture the card and authenticate the payment. Once the payment is confirmed, Glomo credits the customer's account at the partner from its escrow account.

Because the partner shares KYC under regulated-entity reliance, the customer completes no verification step of their own. From the customer's perspective, adding funds from a card is a single, uninterrupted flow.

![Account funding overview](/assets/account-funding-overview.71c0f63b70599a44ccc7c103be87bc848608b7dabfe9817d57645f8a690f5002.8782a9ee.png)

## Prerequisites

- Glomo partner account with API keys, with account funding enabled.
- Customer details including name, date of birth, nationality, overseas address, pincode, last KYC date, and consent to the KYC share. PAN where you hold one.
- HTTPS - secure server endpoint for webhooks.


## Workflow for partners

The sequence diagram below represents the interactions between the customer, the partner bank, the Glomo SDK, and the Glomo API.

![Workflow for partners](/assets/workflow-for-partners.0cd3d47b150c3e7a1f8646ba7f1f5dd3c9fb8bc7e9f6679e99590c97521248fa.8782a9ee.png)

The flow has four parties:

- **Customer** - the account holder adding funds, using their own overseas Visa debit card.
- **Partner bank** - hosts the journey in its own application, calls Glomo's server-side APIs, and credits the funds to the customer's account.
- **Glomo** - quotes the rate, creates the payment account, processes the card payment, runs risk and compliance checks, and settles funds to the partner.
- **Visa** - provides the exchange rate and processes the card payment.


The partner's server calls Glomo to fetch a quote, create the customer, register the beneficiary and create the order. The order is then handed to the Glomo SDK on the client side, which takes over for card capture and payment authentication. Once the payment is authorised and Glomo's risk checks clear, the partner receives a payment success response and returns the customer to its own screens with the appropriate confirmation.

Two points on sequencing. The customer, the beneficiary and the order must all exist before the SDK is invoked, and the order carries the quote - so the rate the customer approves is the rate the payment is authorised at, with no re-pricing in between. And the partner's server never handles card data at any point: the card is captured by the Glomo SDK and passes directly to Glomo's vault, keeping the partner outside PCI scope.

All amounts are integers in minor units. AED and USD carry two decimal places, so `50000` is USD 500.00.

Every call goes to `https://api.glomopay.com/api` and carries your key. The base URL is the same in test and live: which environment a call lands in is decided by the key it carries, so testing the flow is a matter of using your test keys, not a different host. See [Authentication](/platform/authentication) for where to find both sets of keys.

# Integration steps

Below are the steps to integrate the Glomo SDK and enable card-funded top-ups for your customers.

## Create a quote

**`createQuote`** · `POST /v1/quotes` · [API reference](/api-reference/openapi/quote/createquote)

Create a quote from your server by passing the amount to be added and the customer's card currency. Use this API to display the exchange rate, fees, and the total amount that will be debited from the customer's card before they proceed.

Quote in the amount the customer's account is credited, as `destination_amount`. The card debit comes back as `source_amount`. `resource_type` is required on a card quote, and a card quote takes no `payment_method.subtype`.

### API Endpoint

```curl
POST https://api.glomopay.com/api/v1/quotes
```

### Request

```curl
curl -i -X POST \
  https://api.glomopay.com/api/v1/quotes \
  -H 'Authorization: Bearer <YOUR_JWT_HERE>' \
  -H 'Content-Type: application/json' \
  -d '{
    "resource": "payin",
    "resource_type": "account_funding",
    "source_currency": "AED",
    "destination_currency": "USD",
    "destination_amount": 100000,
    "payment_method": {
      "type": "card"
    }
  }'
```

### Response

```curl
{
    "quotes": [
        {
            "id": "quote_6ab599eeKV4e8",
            "resource": "payin",
            "resource_type": "account_funding",
            "expires_at": "2026-09-24T22:15:18Z",
            "payment_method": { "type": "card" },
            "source_currency": "AED",
            "destination_currency": "USD",
            "fx_rate": 0.272258,
            "effective_fx_rate": 0.2700972222222222,
            "source_amount": 373542,
            "destination_amount": 100000,
            "fees": {
                "txn_fee": { "currency": "USD", "amount": 900 },
                "fx_fee": { "currency": "USD", "amount": 800 }
            }
        }
    ]
}
```

> Read `quotes[0]`. An account funding quote returns one element.
`fx_rate` is the mid-market rate. `effective_fx_rate` includes the FX fee but not the transaction fee, so it is not the all-in cost, and it can be `null`. Fees are priced in the destination currency.


## Create a customer

**`createCustomer`** · `POST /v1/customer` · [API reference](/api-reference/openapi/customer/createcustomer)

Create a customer entity in Glomo's system, passing the customer's details and the KYC records you hold for them. To be called when the customer adds funds for the first time. Customer ID has to be stored in your system and reused.

| Field | Required | Notes |
|  --- | --- | --- |
| `country`, `nationality` | Yes | [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3). `IN` is rejected, `IND` is accepted. |
| `dob` | Yes | Mandatory for account funding. Cannot be in the future. |
| `pincode` | Yes | Validated against the country. |
| `kyc` | No | Takes exactly `pan_number` and `last_kyc_date`. `last_kyc_date` may be today, never the future. |


### API Endpoint

```curl
POST https://api.glomopay.com/api/v1/customer
```

### Request

```curl
curl -i -X POST \
  https://api.glomopay.com/api/v1/customer \
  -H 'Authorization: Bearer <YOUR_JWT_HERE>' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Aarav Nair",
    "customer_type": "individual",
    "email": "aarav.nair@example.com",
    "phone": "+919812345678",
    "address": "12 Marina Walk",
    "city": "Kochi",
    "state": "Kerala",
    "country": "IND",
    "pincode": "682001",
    "dob": "1988-04-17",
    "nationality": "IND",
    "kyc": {
      "pan_number": "BFQPG7654R",
      "last_kyc_date": "2026-09-25"
    }
  }'
```

### Response

```curl
{
    "id": "cust_6ab599fdEvVur",
    "name": "Aarav Nair",
    "customer_type": "individual",
    "email": "aarav.nair@example.com",
    "phone": "+919812345678",
    "address": "12 Marina Walk",
    "city": "Kochi",
    "state": "Kerala",
    "country": "IND",
    "pincode": "682001",
    "status": "active",
    "dob": "1988-04-17",
    "nationality": "IND",
    "tax_identification_number": null,
    "created_at": "2026-09-24T21:45:33.539Z",
    "updated_at": "2026-09-24T21:45:33.581Z"
}
```

## Create a beneficiary

**`createBeneficiaryV2`** · `POST /v2/beneficiaries` · [API reference](/api-reference/openapi/beneficiary_v2/createbeneficiaryv2)

Register the account the funds land in. For account funding this is the customer's own account - funds always move from a customer's own card to their own account - and the category is `payout`. Store the `bene_` id and reuse it for the customer's return journey.

### API Endpoint

```curl
POST https://api.glomopay.com/api/v2/beneficiaries
```

### Request

```curl
curl -i -X POST \
  https://api.glomopay.com/api/v2/beneficiaries \
  -H 'Authorization: Bearer <YOUR_JWT_HERE>' \
  -H 'Content-Type: application/json' \
  -d '{
    "category": "payout",
    "beneficiary": {
      "name": "Aarav Nair",
      "type": "individual",
      "email": "aarav.nair@example.com",
      "nickname": "My account",
      "address": {
        "address_line1": "12 Marina Walk",
        "city": "Kochi",
        "state": "Kerala",
        "country_code": "IND",
        "postal_code": "682001"
      }
    },
    "bank_account": {
      "account_number": "000111222333",
      "name": "Aarav Nair",
      "swift_code": "IDFBINBBXXX",
      "currency": "USD",
      "address": {
        "address_line1": "GIFT City, Block 9",
        "city": "Gandhinagar",
        "state": "Gujarat",
        "country_code": "IND"
      }
    }
  }'
```

### Response

```curl
{
    "id": "bene_6ab599fdgflRf",
    "name": "Aarav Nair",
    "category": "payout",
    "email": "aarav.nair@example.com",
    "phone": null,
    "nickname": "My account",
    "status": "active",
    "created_at": "2026-09-24T21:45:33Z",
    "updated_at": "2026-09-24T21:45:33Z",
    "notes": null,
    "type": "individual",
    "address": {
        "address_line1": "12 Marina Walk",
        "address_line2": null,
        "city": "Kochi",
        "state": "Kerala",
        "country_code": "IND",
        "postal_code": "682001"
    },
    "bank_account": {
        "account_number": "000111222333",
        "name": "Aarav Nair",
        "swift_code": "IDFBINBBXXX",
        "local_routing_number": null,
        "currency": "USD",
        "category": null,
        "address": {
            "address_line1": "GIFT City, Block 9",
            "address_line2": null,
            "city": "Gandhinagar",
            "state": "Gujarat",
            "country_code": "IND"
        }
    }
}
```

## Create an order on the server

**`createOrder`** · `POST /v1/orders` · [API reference](/api-reference/openapi/orders/createorder)

Initiate the payment by creating an order from your server using your API key, the customer ID, the beneficiary ID and the quote ID from step 1. The `id` returned in the response should be passed to the SDK. This ensures the payment is securely linked to the correct order and prevents tampering.

Send no `amount` and no `currency`: the order reads both from the quote, along with its expiry. `request_id` is the idempotency key - replaying it returns the original order instead of creating a second one.

### API Endpoint

```curl
POST https://api.glomopay.com/api/v1/orders
```

### Request

```curl
curl -i -X POST \
  https://api.glomopay.com/api/v1/orders \
  -H 'Authorization: Bearer <YOUR_JWT_HERE>' \
  -H 'Content-Type: application/json' \
  -d '{
    "customer_id": "cust_6ab599fdEvVur",
    "quote_id": "quote_6ab599eeKV4e8",
    "beneficiary_id": "bene_6ab599fdgflRf",
    "request_id": "bank-ref-0001",
    "notes": {
      "channel": "mobile_app"
    }
  }'
```

### Response

```curl
{
    "id": "order_6ab59a06f9xuk",
    "customer_id": "cust_6ab599fdEvVur",
    "document_id": null,
    "status": "active",
    "currency": "USD",
    "amount": 100000,
    "purpose_code": null,
    "invoice_amount": null,
    "invoice_description": null,
    "invoice_number": null,
    "notes": { "channel": "mobile_app" },
    "expires_at": "2026-09-24T22:15:18Z",
    "created_at": "2026-09-24T21:45:42Z",
    "updated_at": "2026-09-24T21:45:43Z",
    "reference_number": null
}
```

Orders are declined where the transaction would exceed the applicable transaction limits, or where the quote or beneficiary does not hold up. A declined order answers `422` with a machine-readable `code` beside the message:

| Code | Meaning |
|  --- | --- |
| `quote_not_found` | No quote with that id. |
| `quote_not_owned` | The quote belongs to another business. |
| `quote_expired` | The quote lapsed. Create a new one. |
| `quote_resource_type_missing` | The quote did not declare `account_funding`. |
| `beneficiary_missing` | No beneficiary named, or it does not resolve. |
| `order_amount_above_cap` | Above the configured cap for that currency. |
| `order_cap_unavailable` | No cap is configured for that currency. Not the same as the amount being too large. |
| `payment_method_unavailable` | Your account has no active card payment method. |


`order_cap_unavailable` and `payment_method_unavailable` share the same customer-safe message, so branch on `code`, never on `message`. The cap applies to the destination currency - the amount credited, not the amount charged to the card.

## Integration Guide

1. Integrate an "Add funds" button into your client-side interface.
2. Add the code snippet below to your website or app to launch Glomo's SDK.
3. When the customer taps the button, the checkout opens using the `order_id` created in step 4.
4. Handle payment initiation success and failure events using the provided handler functions.
5. On successful debit, you'll receive either a payment object (on successful authorisation by the customer) or an error object (on failure).
6. Send this information back to your server to redirect the user or to trigger a retry if the payment failed.


An account funding order uses the Unified SDK's Order Checkout journey. See the [Glomo Unified SDK](/platform/sdk/unified-sdk) for the web integration, and [Web Checkout in a Mobile App (WebView)](/platform/sdk/web-checkout-in-mobile-app) to open checkout inside a native app, which navigates to the hosted entry URL as a top-level document:

```
https://api.glomopay.com/api/public/v1/checkout/redirect?resource_id=order_6ab59a06f9xuk&public_key={PUBLIC_KEY}
```

Navigate to it; do not `fetch()` it. Do not pass `mode` - the checkout derives the environment from your public key's prefix, so the same URL serves test and live.

### Add the payment button on your website

```html
<button id="add-funds-button">Add funds</button>
<script type="module">
  import { GlomoCheckoutApi } from 'https://unified-sdk.glomopay.com/index.js';

  const checkout = new GlomoCheckoutApi({
    publicKey: '<YOUR_PUBLIC_KEY>',   // Your public key from the Glomo dashboard
    orderId: 'order_6ab59a06f9xuk',   // Order ID from the previous step
  });

  checkout.on('payment.success', function (response) {
    // response: { orderId, paymentId, status, signature }
    window.location.href = '/add-funds/result?order=' + response.orderId;
  });

  checkout.on('payment.failure', function (response) {
    // response: { orderId, paymentId, status, error: { code, message }, customerErrorMessage }
    window.location.href = '/add-funds/failed?order=' + response.orderId;
  });

  checkout.on('checkout.closed', function () {
    // Customer closed the checkout. Nothing was charged.
  });

  document.getElementById('add-funds-button').addEventListener('click', () => {
    checkout.open();
  });
</script>
```

### Add the payment button on your React Native app

```tsx
import React, { useRef } from 'react';
import { View, Button } from 'react-native';
import { GlomoCheckout, type GlomoCheckoutRef, type GlomoCheckoutPayload, type SdkError } from '@glomopay/react-native-sdk';

export default function AddFundsScreen() {
  const checkoutRef = useRef<GlomoCheckoutRef>(null);

  const handleAddFunds = async () => {
    const started = await checkoutRef.current?.start();
    if (!started) {
      // Could not start. onSdkError carries the reason.
    }
  };

  return (
    <View style={{ flex: 1 }}>
      <Button title="Add funds" onPress={handleAddFunds} />

      <GlomoCheckout
        ref={checkoutRef}
        publicKey="<YOUR_PUBLIC_KEY>"
        orderId="order_6ab59a06f9xuk"
        onPaymentSuccess={(payload: GlomoCheckoutPayload) => {
          // Send payload to your server and verify the signature there.
        }}
        onPaymentFailure={(payload: GlomoCheckoutPayload) => {
          // No charge was made. Offer a retry on a new order.
        }}
        onPaymentTerminate={() => {
          // Customer dismissed the checkout.
        }}
        onConnectionError={(error) => {
          // Network failure. Let the customer retry.
        }}
        onSdkError={(errors: SdkError[]) => {
          // Configuration or device-security problems. Fall back to your other funding options.
        }}
      />
    </View>
  );
}
```

### Completion events

Checkout reports the outcome by posting a message to its host. A web integration receives these through the SDK; a WebView host listens on its own bridge.

```curl
{
    "type": "payment.success",
    "payload": {
        "payment_id": "payt_6aabd2d1oXGRy",
        "order_id": "order_6ab59a06f9xuk",
        "status": "success",
        "signature": "<hmac-sha256>"
    }
}
```

| Event | Meaning |
|  --- | --- |
| `payment.success` | Card authorised. Verify the signature server-side before crediting anything. |
| `payment.failure` | Card declined or abandoned. The order stays payable for a retry. |
| `checkout.closed` | The customer closed checkout without completing. Nothing was charged. |


## What the customer sees in the SDK

The SDK handles the full card journey inside your application:

**Card entry.** The customer enters their card number. Glomo checks the card's eligibility before any payment is attempted, and confirms inline that the card can be used. Cards that are not eligible are declined at this point with a clear message and without any authorisation being attempted against the card - the customer can try a different card or return to your screens and use another funding method.

**Payment confirmation.** The customer sees the amount to be credited, the exchange rate, and the exact debit in their card currency, matching the quote from step 1.

![Glomo checkout on web and mobile](/assets/checkout-screens.e12fcc4beb179ff0cee22cc1fae6633cb28e1422fc2035026a71efa63249caa0.8782a9ee.png)

The SDK returns control to your application on completion, cancellation, or failure. Card data is never exposed to your application at any point.

## Verify the SDK response and signature on the server

The signature is `HMAC-SHA256(order_id|payment_id|status)` with your secret key. Never credit a customer on a client-side signal alone. The process to verify the signature can be found [here](/payin/checkout).

## Verify payment status

Every payment carries two independent statuses, and both must be consumed to determine what to show the customer.

- **Payment status** reflects the card debit - whether the customer's card was successfully charged.
- **Compliance status** reflects Glomo's compliance clearance on the transaction. Most payments are approved automatically once screening completes, with no action from you. Where screening flags a payment the status becomes `action_required` and Glomo requests what it needs; the payment is approved once that is reviewed and accepted, or rejected if it is not.


A payment is only complete when the payment status is `success` and the compliance status is `approved`. A successful debit alone is not sufficient - funds are released to the customer's account only once compliance has also been approved.

Handle the combinations as follows:

| Payment status | Compliance status | What to show the customer |
|  --- | --- | --- |
| success | approved | Funds added successfully. |
| success | action_required | Payment received, funds being credited shortly. |
| failed | (not set) | Payment could not be completed, offer retry. |
| success | rejected | Card was debited but compliance rejected the payment. Glomo refunds it to the card. |


The case to design for deliberately is **success with compliance `action_required`**: the customer's card has been debited but their account is not yet credited. Never present this as a failure - the customer has paid. Show it as in progress, and confirm to the customer once the compliance-approved webhook arrives.

Payment status can be tracked via the following ways:

1. **Dashboard Monitoring** - view real-time updates for all payments in the Glomo dashboard.
2. **Payment API Polling** - programmatically check payment status using Glomo's Payment APIs.
3. **Webhook Notification** - get instant notifications on payment status changes directly to your server.


You can also poll the order with [`getOrderById`](/api-reference/openapi/orders/getorderbyid) for the credited amount. `paid` is the success value, not `success`. A failed payment does not move the order - it stays `active` so it can be retried - so the order alone cannot tell you a payment failed. Take failure from the checkout event or the payment, and use the order for the credited amount.

## Sandbox Payment Status Handling

In production, payment status is updated automatically on confirmation from Visa, and compliance status updates when screening completes. In sandbox, there is no real card network confirmation, so outcomes are driven by test cards - each card number deterministically produces a specific payment and compliance status, allowing you to exercise every path in your integration without manual intervention.

For every test card the CVV is the last three digits of the card number and the expiry is `12` / five years from the current year.

| Card number | Payment status | Compliance status | Eligible for account funding |
|  --- | --- | --- | --- |
| 4622011111111115 | Success | approved | Yes |
| 4622022222222220 | Success | action_required | Yes |
| 4622033333333335 | Failed | Not applicable | Yes |
| 4622044444444440 | Failed | Not applicable | No |
| 4622055555555550 | Success | approved | No |


An ineligible card is declined at card entry, before any authorisation is attempted.

Payments made with test cards appear in the sandbox dashboard alongside their statuses, and webhooks fire for each status change exactly as they do in production.

Your sandbox account also needs account funding enabled, an order cap for the destination currency, an active card payment method, and pricing rules for both the transaction fee and the FX fee. Without them, order creation is declined with one of the codes above.