Skip to main content
POST
/
payments
/
charge
curl -X POST https://api.khaime.com/api/v1/partner/payments/charge \
  -H "X-API-Key: pk_sandbox_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "preview": true,
    "amount": 1440000,
    "currency": "NGN",
    "total_amount": 9000,
    "total_currency": "USD",
    "description": "Design consultation",
    "customer": {
      "email": "jane@example.com",
      "first_name": "Jane",
      "last_name": "Doe",
      "country": "NG"
    }
  }'

Documentation Index

Fetch the complete documentation index at: https://docs.khaime.com/llms.txt

Use this file to discover all available pages before exploring further.

Create Charge

Create a one-time payment charge without requiring a product in Khaime’s catalog. Ideal for WooCommerce or custom integrations where products live on the merchant’s platform.
This is the recommended endpoint for plugin integrations. Use Create Session if you need Stripe Checkout hosted pages instead.

Request Body

amount
integer
required
Charge amount in smallest currency unit (cents, kobo) — the customer’s payment currency. Must be calculated using /pricing/calculate when currency conversion is involved — we validate it matches our calculation.
currency
string
required
3-letter ISO currency code for what the customer pays. Example: USD, NGN, GBP
total_amount
integer
required
The full amount the merchant is collecting, in smallest currency unit of total_currency. This is inclusive of all fees (shipping, handling, etc.) — not just the base product price.Why required: This field is the source of truth for what gets credited to the merchant’s wallet, and anchors our server-side conversion when multicurrency is involved.
  • No conversion: Pass the same value as amount
  • Multicurrency: Pass the total in the merchant’s baseline currency (e.g. 8500 for $85.00 USD)
  • Marketplace: Pass the total in the sub-merchant’s baseline currency
Example: merchant prices a service at 85USDwith85 USD with 5 shipping = $90 total, customer pays ₦144,000 NGN — set total_amount: 9000, total_currency: "USD".
original_product_amount is accepted as a deprecated alias and will continue to work.
total_currency
string
required
ISO 4217 currency code for total_amount. This is the merchant’s (or sub-merchant’s) pricing currency.
  • No conversion: Pass the same value as currency
  • Multicurrency / Marketplace: Pass the merchant’s baseline currency
original_product_currency is accepted as a deprecated alias and will continue to work.
converted_total_amount
integer
Your pre-computed conversion of total_amount into currency (the customer’s payment currency). Optional — but strongly recommended when total_currency differs from currency.When provided, we cross-validate:
  1. converted_total_currency must equal currency
  2. converted_total_amount must equal amount (within 1% tolerance)
This lets us catch discrepancies before hitting the exchange-rate API and gives you an explicit paper trail of what rate you applied.Must be provided together with converted_total_currency.
converted_total_currency
string
The currency of converted_total_amount. Must match currency. Required when converted_total_amount is provided.
description
string
Human-readable description of the charge. Shown on payment receipts.
reference
string
Your unique reference for this charge. Used for idempotency and reconciliation.
callback_url
string
Canonical redirect URL after payment.
  • Paystack: customer returns here after hosted checkout
  • StartButton: sent through as redirectUrl during payment initialization
  • Stripe: use return_url in confirmPayment() on the client
redirectUrl
string
Camel-case alias for callback_url. Useful for StartButton-oriented integrations.
redirect_url
string
Snake-case alias for callback_url.
customer
object
required
user_country
string
Alias for customer.country. Accepted as a top-level field for storefronts that already send user_country.
customer_country
string
Alias for customer.country. Accepted as a top-level field and inside metadata.
country_code
string
Alias for customer.country. Accepted as a top-level field.
billing_country
string
Billing country fallback when customer.country is absent. Accepted as a top-level field and inside metadata.
shipping_country
string
Shipping country fallback when customer.country is absent. Accepted as a top-level field and inside metadata.
Send any country value you already have. Khaime normalizes common country names and ISO-2 codes, then stores the resolved value on the transaction for customer geography reports. If no country is provided, Stripe/Paystack card country may be used after payment; otherwise the transaction may appear as Unknown in geography analytics.
metadata
object
Custom key-value pairs attached to the charge. Values must be strings, numbers, or booleans — no nested objects.
preview
boolean
default:"false"
When true, returns a fee and conversion breakdown without creating any database records or calling any payment gateway. Use this to show the customer exactly what they’ll pay before initiating the real charge.All validation still runs — amount mismatch, currency conversion, sub-merchant, etc. — so a successful preview response means your actual charge will go through.See Preview Mode below for the response shape.

Marketplace Fields

This field is only relevant if your account is set up as a marketplace operator. See Setup Marketplace to enable marketplace mode.
sub_merchant_id
integer
The Khaime educator ID of the sub-merchant this charge is being collected for. Must have active status in your marketplace.When present, the webhook additionally credits the sub-merchant’s wallet for total_amount minus your marketplace commission, in total_currency. Your marketplace wallet is still credited as normal.

How the split works

On webhook receipt after a successful payment:
  1. Your marketplace wallet is credited for amount minus the Khaime platform fee — same as a standard charge.
  2. The sub-merchant wallet is additionally credited for total_amount minus your commission, in total_currency. No additional platform fee is deducted from the sub-merchant — it was already taken on the full charge.
Commission is resolved in this order:
  • Per-merchant commission_rate set via Update Commission
  • Your portfolio default marketplace_commission_rate
  • 0 if neither is set
The sub-merchant wallet transaction stores the full breakdown in conversion_details:
{
  "source": "marketplace_split",
  "marketplace_id": 15,
  "charge_currency": "NGN",
  "gross_product_amount": 140000,
  "commission_rate": 0.05,
  "commission_amount": 7000,
  "net_credited": 133000,
  "cart_identifier": "intent_abc123"
}
The sub-merchant must have status: active in your marketplace. Pending or suspended sub-merchants will be skipped — your marketplace wallet will still be credited normally.

Response

The response varies based on which gateway Khaime routes to:
{
  "success": true,
  "data": {
    "charge_id": "charge_a1b2c3d4",
    "payment_gateway": "stripe",
    "client_secret": "pi_3Abc_secret_Xyz",
    "publishable_key": "pk_test_...",
    "stripe_account_id": "acct_1ABC...",
    "transaction_id": "456",
    "fee_details": {
      "base_amount": 5000,
      "transaction_fee": 320,
      "total_amount": 5320,
      "customer_pays_fees": true
    }
  }
}
Use client_secret and publishable_key to mount Stripe Payment Element.

Gateway Routing

The currency field determines which gateway processes the payment:
CurrencyGateway
NGNPaystack
GHS, KES, ZAR, TZS, XAF, XOFStartButton
USD, EUR, GBP, CAD, AUDStripe

Preview Mode

Pass "preview": true in the request body to calculate fees and FX conversion without creating a charge. The response is identical in structure to a normal charge response except:
  • preview: true is set at the top level
  • No charge_id, client_secret, payment_url, or transaction_id — no gateway was called
  • No database records are created
{
  "success": true,
  "message": "Charge preview calculated successfully",
  "data": {
    "preview": true,
    "payment_gateway": "paystack",
    "mode": "sandbox",
    "is_international": true,
    "business_country": "US",
    "customer_country": "NG",
    "customer": {
      "currency": "NGN",
      "base_amount": 1440000,
      "transaction_fee": 92160,
      "total_charged": 1440000,
      "pays_fees": false
    },
    "business": {
      "currency": "USD",
      "priced_amount": 9000
    },
    "conversion": {
      "original_amount": 9000,
      "original_currency": "USD",
      "converted_amount": 1440000,
      "converted_currency": "NGN",
      "exchange_rate": 1600
    }
  }
}
customer and business are always in different currencies when multicurrency is involved — do not compare customer.base_amount with business.priced_amount directly. Use the conversion.exchange_rate to relate them. conversion is only present when currency differs from total_currency.
Typical workflow:
1. POST /payments/charge  { preview: true, ...fields }
   → Show breakdown to customer
2. Customer confirms
3. POST /payments/charge  { ...same fields, no preview }
   → Actual charge created, payment URL / client secret returned

Amount Validation (Double Check)

Both you and Khaime calculate the charge amount independently — they must match. Your workflow:
  1. Call /pricing/calculate with total_amount and target_currency
  2. Use the returned local.amount as your amount in the charge request
  3. Optionally, pass converted_total_amount and converted_total_currency to make your pre-computed conversion explicit
  4. We recalculate using the same conversion service and verify they match (1% tolerance for rounding)
Why double validation? This ensures both parties agree on the conversion. If there’s a mismatch, something is wrong — either a bug, stale rate, or manipulation attempt.
# Step 1: Get the converted amount
curl "https://api.khaime.com/api/v1/partner/pricing/calculate?amount=9000&target_currency=NGN" \
  -H "X-API-Key: pk_sandbox_your_key"
# Returns: { "local": { "amount": 1440000, "currency": "NGN" } }

# Step 2: Use that amount in your charge
curl -X POST "https://api.khaime.com/api/v1/partner/payments/charge" \
  -H "X-API-Key: pk_sandbox_your_key" \
  -d '{
    "amount": 1440000,
    "currency": "NGN",
    "total_amount": 9000,
    "total_currency": "USD",
    "converted_total_amount": 1440000,
    "converted_total_currency": "NGN",
    ...
  }'

Error Codes

StatusErrorFix
400Amount mismatchYour amount doesn’t match our calculation. Use /pricing/calculate to get the correct amount.
400converted_total_amount does not match amountYour pre-computed conversion differs from amount. Ensure both represent the charge total in the same currency.
400converted_total_currency must match the charge currencyconverted_total_currency must equal currency.
400Both converted_total_amount and converted_total_currency must be provided togetherSupply both fields or neither.
422"amount" is requiredInclude amount — use /pricing/calculate to convert total_amount to the customer’s currency.
422Missing total_amount / total_currencyInclude total_amount and total_currency (or their aliases original_product_amount / original_product_currency).
400Unable to convert from X to YCurrency conversion failed — try again or use a supported currency.
400Missing required fieldsInclude amount, currency, customer.email.
401Invalid API keyCheck your X-API-Key header.
422Metadata validation failedEnsure metadata values are scalar (no nested objects).
curl -X POST https://api.khaime.com/api/v1/partner/payments/charge \
  -H "X-API-Key: pk_sandbox_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "preview": true,
    "amount": 1440000,
    "currency": "NGN",
    "total_amount": 9000,
    "total_currency": "USD",
    "description": "Design consultation",
    "customer": {
      "email": "jane@example.com",
      "first_name": "Jane",
      "last_name": "Doe",
      "country": "NG"
    }
  }'