Skip to main content
POST

Intro

Create a one-time (or recurring, with a subscription field) payment charge without needing the product in Khaime’s catalog — the endpoint of choice for WooCommerce and custom integrations where products live on the merchant’s own platform.
This is the recommended endpoint for plugin integrations. Use Create Session if your product already lives in Khaime’s catalog.

Context

Create Charge is the entry point for the whole payment flow described in Payments Overview: the currency you send drives Gateway Routing, cross-currency amounts are validated against Calculate Pricing, subscription_frequency_key turns the charge into a subscription, and a successful charge’s transaction_id is what you later pass to Refunds. Payment outcome is confirmed over the payment.succeeded / payment.failed webhooks, not a return value from this call.

Hows

Request body

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.
string
required
3-letter ISO currency code for what the customer pays. Example: USD, NGN, GBP
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.
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.
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.
string
The currency of converted_total_amount. Must match currency. Required when converted_total_amount is provided.
string
Human-readable description of the charge. Shown on payment receipts.
string
Your unique reference for this charge. Used for idempotency and reconciliation.
string
Canonical redirect URL after payment.
  • Paystack: customer returns here after hosted checkout
  • StartButton: sent through as redirectUrl during payment initialization
  • Embedded: use return_url in confirmPayment() on the client
string
Camel-case alias for callback_url. Useful for StartButton-oriented integrations.
string
Snake-case alias for callback_url.
string
Set to make this a recurring charge instead of a one-time payment — see Subscriptions for valid keys and behavior.
object
required
string
Alias for customer.country. Accepted as a top-level field for storefronts that already send user_country.
string
Alias for customer.country. Accepted as a top-level field and inside metadata.
string
Alias for customer.country. Accepted as a top-level field.
string
Billing country fallback when customer.country is absent. Accepted as a top-level field and inside metadata.
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, embedded/redirect card country may be used after payment; otherwise the transaction may appear as Unknown in geography analytics.
object
Custom key-value pairs attached to the charge. Send strings, numbers, or booleans only — nested objects/arrays are not rejected by validation (there is no scalar-type check server-side), but they can fail silently downstream if the resolved gateway’s API rejects non-string metadata values. Treat this as a hard rule, not just a recommendation.
string
Force a specific gateway instead of Khaime’s automatic currency-based routing (see Gateway Routing). Only stripe or paystack are accepted — you cannot force startbutton or flutterwave through this field, regardless of currency.
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.
integer
The Khaime business 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:
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:
Use client_secret and publishable_key to mount the embedded payment element. When merchant_account_id is present (direct/marketplace charges), pass it as connectedAccount when initializing Khaime Payment SDK.
Want Apple Pay / Google Pay in the Payment Element? Register your checkout domain first — see Register Payment Method Domain.

Gateway routing

The currency field determines which gateway processes the payment — see Gateway Routing for the full mapping:

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
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:

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
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.
There are two different tolerance checks, easy to conflate: if you supply converted_total_amount, it’s compared against amount with a 1% tolerance. Separately, whenever the server recalculates the expected charge amount itself (the normal path when converted_total_amount is omitted), it uses a wider 2% tolerance to absorb exchange-rate cache drift between your request and Khaime’s own rate refresh.

Error codes

Whys

total_amount / total_currency are required (not just amount / currency) because they’re the source of truth for what the merchant is owed — amount alone tells Khaime what the customer paid, but not what that should convert back to for wallet crediting. Requiring both, plus the double-validation described above, lets Khaime catch a wrong conversion before money moves rather than after. Marketplace commission resolves through a fallback chain (per-merchant rate → portfolio default → zero) so operators can set sane platform-wide defaults without having to configure every sub-merchant individually.

Why nots

  • This endpoint does not require a Khaime catalog product — if you want catalog-backed checkout instead, use Create Payment Intent.
  • payment_gateway cannot force startbutton or flutterwave — only stripe and paystack are valid overrides.
  • Metadata values are not schema-validated for type — sending nested objects/arrays won’t be rejected here, but can fail silently at the gateway. Keep values scalar.
  • A charge to a pending or suspended sub-merchant is not rejected outright — it’s silently skipped for the sub-merchant credit while your marketplace wallet is still credited normally, so check sub_merchant_id status ahead of time if the split is load-bearing for your flow.