Payments
Create Charge
Create a product-agnostic payment charge.
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: thecurrency 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, GBPinteger
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.
8500for $85.00 USD) - Marketplace: Pass the total in the sub-merchant’s baseline currency
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:converted_total_currencymust equalcurrencyconverted_total_amountmust equalamount(within 1% tolerance)
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
redirectUrlduring payment initialization - Embedded: use
return_urlinconfirmPayment()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.- Your marketplace wallet is credited for
amountminus the Khaime platform fee — same as a standard charge. - The sub-merchant wallet is additionally credited for
total_amountminus your commission, intotal_currency. No additional platform fee is deducted from the sub-merchant — it was already taken on the full charge.
- Per-merchant
commission_rateset via Update Commission - Your portfolio default
marketplace_commission_rate 0if neither is set
conversion_details:
Response
The response varies based on which gateway Khaime routes to:- Embedded
- Paystack
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
Thecurrency 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: trueis set at the top level- No
charge_id,client_secret,payment_url, ortransaction_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.Amount validation (double check)
Both you and Khaime calculate the charge amount independently — they must match. Your workflow:- Call /pricing/calculate with
total_amountandtarget_currency - Use the returned
local.amountas youramountin the charge request - Optionally, pass
converted_total_amountandconverted_total_currencyto make your pre-computed conversion explicit - We recalculate using the same conversion service and verify they match
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_gatewaycannot forcestartbuttonorflutterwave— onlystripeandpaystackare 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_idstatus ahead of time if the split is load-bearing for your flow.
