Create Payment Intent
curl --request POST \
--url https://api.khaime.com/api/v1/payment/intent \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"charge_amount": 123,
"charge_currency": "<string>",
"merchant_amount": 123,
"merchant_currency": "<string>",
"description": "<string>",
"reference": "<string>",
"callback_url": "<string>",
"subscription_frequency_key": "<string>",
"customer": {
"email": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"country": "<string>"
},
"metadata": {},
"preview": true
}
'import requests
url = "https://api.khaime.com/api/v1/payment/intent"
payload = {
"charge_amount": 123,
"charge_currency": "<string>",
"merchant_amount": 123,
"merchant_currency": "<string>",
"description": "<string>",
"reference": "<string>",
"callback_url": "<string>",
"subscription_frequency_key": "<string>",
"customer": {
"email": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"country": "<string>"
},
"metadata": {},
"preview": True
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
charge_amount: 123,
charge_currency: '<string>',
merchant_amount: 123,
merchant_currency: '<string>',
description: '<string>',
reference: '<string>',
callback_url: '<string>',
subscription_frequency_key: '<string>',
customer: {
email: '<string>',
first_name: '<string>',
last_name: '<string>',
country: '<string>'
},
metadata: {},
preview: true
})
};
fetch('https://api.khaime.com/api/v1/payment/intent', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.khaime.com/api/v1/payment/intent",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'charge_amount' => 123,
'charge_currency' => '<string>',
'merchant_amount' => 123,
'merchant_currency' => '<string>',
'description' => '<string>',
'reference' => '<string>',
'callback_url' => '<string>',
'subscription_frequency_key' => '<string>',
'customer' => [
'email' => '<string>',
'first_name' => '<string>',
'last_name' => '<string>',
'country' => '<string>'
],
'metadata' => [
],
'preview' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.khaime.com/api/v1/payment/intent"
payload := strings.NewReader("{\n \"charge_amount\": 123,\n \"charge_currency\": \"<string>\",\n \"merchant_amount\": 123,\n \"merchant_currency\": \"<string>\",\n \"description\": \"<string>\",\n \"reference\": \"<string>\",\n \"callback_url\": \"<string>\",\n \"subscription_frequency_key\": \"<string>\",\n \"customer\": {\n \"email\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"country\": \"<string>\"\n },\n \"metadata\": {},\n \"preview\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.khaime.com/api/v1/payment/intent")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"charge_amount\": 123,\n \"charge_currency\": \"<string>\",\n \"merchant_amount\": 123,\n \"merchant_currency\": \"<string>\",\n \"description\": \"<string>\",\n \"reference\": \"<string>\",\n \"callback_url\": \"<string>\",\n \"subscription_frequency_key\": \"<string>\",\n \"customer\": {\n \"email\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"country\": \"<string>\"\n },\n \"metadata\": {},\n \"preview\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.khaime.com/api/v1/payment/intent")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"charge_amount\": 123,\n \"charge_currency\": \"<string>\",\n \"merchant_amount\": 123,\n \"merchant_currency\": \"<string>\",\n \"description\": \"<string>\",\n \"reference\": \"<string>\",\n \"callback_url\": \"<string>\",\n \"subscription_frequency_key\": \"<string>\",\n \"customer\": {\n \"email\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"country\": \"<string>\"\n },\n \"metadata\": {},\n \"preview\": true\n}"
response = http.request(request)
puts response.read_bodyPayments
Create Payment Intent
Create a payment intent that works seamlessly with the Khaime SDK.
POST
/
payment
/
intent
Create Payment Intent
curl --request POST \
--url https://api.khaime.com/api/v1/payment/intent \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"charge_amount": 123,
"charge_currency": "<string>",
"merchant_amount": 123,
"merchant_currency": "<string>",
"description": "<string>",
"reference": "<string>",
"callback_url": "<string>",
"subscription_frequency_key": "<string>",
"customer": {
"email": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"country": "<string>"
},
"metadata": {},
"preview": true
}
'import requests
url = "https://api.khaime.com/api/v1/payment/intent"
payload = {
"charge_amount": 123,
"charge_currency": "<string>",
"merchant_amount": 123,
"merchant_currency": "<string>",
"description": "<string>",
"reference": "<string>",
"callback_url": "<string>",
"subscription_frequency_key": "<string>",
"customer": {
"email": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"country": "<string>"
},
"metadata": {},
"preview": True
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
charge_amount: 123,
charge_currency: '<string>',
merchant_amount: 123,
merchant_currency: '<string>',
description: '<string>',
reference: '<string>',
callback_url: '<string>',
subscription_frequency_key: '<string>',
customer: {
email: '<string>',
first_name: '<string>',
last_name: '<string>',
country: '<string>'
},
metadata: {},
preview: true
})
};
fetch('https://api.khaime.com/api/v1/payment/intent', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.khaime.com/api/v1/payment/intent",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'charge_amount' => 123,
'charge_currency' => '<string>',
'merchant_amount' => 123,
'merchant_currency' => '<string>',
'description' => '<string>',
'reference' => '<string>',
'callback_url' => '<string>',
'subscription_frequency_key' => '<string>',
'customer' => [
'email' => '<string>',
'first_name' => '<string>',
'last_name' => '<string>',
'country' => '<string>'
],
'metadata' => [
],
'preview' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.khaime.com/api/v1/payment/intent"
payload := strings.NewReader("{\n \"charge_amount\": 123,\n \"charge_currency\": \"<string>\",\n \"merchant_amount\": 123,\n \"merchant_currency\": \"<string>\",\n \"description\": \"<string>\",\n \"reference\": \"<string>\",\n \"callback_url\": \"<string>\",\n \"subscription_frequency_key\": \"<string>\",\n \"customer\": {\n \"email\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"country\": \"<string>\"\n },\n \"metadata\": {},\n \"preview\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.khaime.com/api/v1/payment/intent")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"charge_amount\": 123,\n \"charge_currency\": \"<string>\",\n \"merchant_amount\": 123,\n \"merchant_currency\": \"<string>\",\n \"description\": \"<string>\",\n \"reference\": \"<string>\",\n \"callback_url\": \"<string>\",\n \"subscription_frequency_key\": \"<string>\",\n \"customer\": {\n \"email\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"country\": \"<string>\"\n },\n \"metadata\": {},\n \"preview\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.khaime.com/api/v1/payment/intent")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"charge_amount\": 123,\n \"charge_currency\": \"<string>\",\n \"merchant_amount\": 123,\n \"merchant_currency\": \"<string>\",\n \"description\": \"<string>\",\n \"reference\": \"<string>\",\n \"callback_url\": \"<string>\",\n \"subscription_frequency_key\": \"<string>\",\n \"customer\": {\n \"email\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"country\": \"<string>\"\n },\n \"metadata\": {},\n \"preview\": true\n}"
response = http.request(request)
puts response.read_bodyIntro
Create a payment intent for one-time or recurring payments without needing products in Khaime’s catalog. Returns a signedtoken that you pass to <KhaimeCheckout /> — the Khaime SDK handles all payment gateway logic automatically.
No gateway SDKs required. You don’t need to install Stripe, Paystack, or any other payment SDK. Just use
@khaime/react and the token handles everything.Using Khaime Catalog Products?
If your products are in Khaime’s catalog (storefronts), use Create Product Payment Intent instead. It supports multi-item carts, shipping calculations, and variant selection.
How It Works
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Your Frontend │────▶│ Your Backend │────▶│ Khaime API │
│ │ │ │ │ │
│ Checkout Form │ │ POST /checkout │ │ POST /payment │
│ + Customer │ │ (your route) │ │ /intent │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Returns token with gateway config │
│ (gateway selected automatically) │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ <KhaimeCheckout token={token} /> │
│ Renders correct payment UI automatically │
└─────────────────────────────────────────────┘
- Your backend calls Khaime API and gets a
token - Token contains everything needed for payment (gateway, keys, amount)
- Your code never touches Stripe/Paystack/etc directly
- Gateway is selected automatically based on currency
Quick Start
1. Create Payment Intent (Backend)
curl -X POST https://api.khaime.com/api/v1/payment/intent \
-H "X-API-Key: pk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"charge_amount": 5000,
"charge_currency": "USD",
"merchant_amount": 5000,
"merchant_currency": "USD",
"description": "Order #123",
"customer": {
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe"
}
}'
2. Response
{
"status": true,
"message": "Payment charge created successfully",
"data": {
"token": "eyJtZXJjaGFudF9pZCI6MTc5MCwicGF5bWVudF9nYXRld2F5Ijoic3RyaXBlIi...",
"charge_id": "intent_abc123-def456-789",
"amount": 5000,
"currency": "USD",
"status": "pending",
"mode": "live",
"breakdown": {
"subtotal": 5000,
"total": 5000,
"customer_pays_fees": false,
"is_international": false
},
"expires_at": "2024-01-15T10:30:00.000Z"
}
}
3. Render Checkout (Frontend)
import { KhaimeCheckout } from '@khaime/react';
function CheckoutPage({ token }) {
return (
<KhaimeCheckout
token={token}
onSuccess={(result) => {
console.log('Payment successful!', result);
window.location.href = '/success';
}}
onError={(error) => {
console.error('Payment failed:', error);
}}
onClose={() => {
console.log('Checkout closed');
}}
/>
);
}
Request Body
Every payment intent requires two amounts:| Field | Description | Example |
|---|---|---|
merchant_amount / merchant_currency | What you priced the product at (your currency) | $90 USD |
charge_amount / charge_currency | What the customer pays (their currency) | ₦144,000 NGN |
Same currency? If customer pays in your currency, both amounts are identical:Different currency? Use /pricing/calculate to convert, then pass both:
{ "merchant_amount": 5000, "merchant_currency": "USD", "charge_amount": 5000, "charge_currency": "USD" }
{ "merchant_amount": 9000, "merchant_currency": "USD", "charge_amount": 1440000, "charge_currency": "NGN" }
Customer Payment (What they pay)
integer
required
The amount to charge the customer, in smallest currency unit (cents, kobo, etc.).
- If same currency as merchant: same value as
merchant_amount - If different currency: use the
converted_amountfrom /pricing/calculate
Alias:
amountstring
required
The currency the customer pays in. 3-letter ISO code (e.g.,
USD, NGN, GBP).This determines which payment gateway is used (Stripe for USD/EUR/GBP, Paystack for NGN, etc.).Alias:
currencyMerchant Settlement (What you receive)
integer
required
The amount you priced the product at, in smallest currency unit. This is your guaranteed settlement amount.Khaime guarantees you receive exactly this amount regardless of exchange rate fluctuations between when the customer pays and when you’re settled.
Alias:
total_amountstring
required
Your settlement currency. 3-letter ISO code (e.g.,
USD for US merchant).Alias:
total_currencyAnti-fraud validation: Khaime recalculates the conversion server-side and rejects if
charge_amount doesn’t match merchant_amount at current exchange rates (0.02% tolerance). This prevents partners from overcharging customers.string
Human-readable description of the charge. Shown on payment receipts.
string
Your unique reference for this charge. Used for idempotency and reconciliation.
string
Redirect URL after payment completes. Used for redirect-based flows.
string
Set to make this a recurring charge instead of a one-time payment. See Subscriptions for valid keys.
object
required
object
Custom key-value pairs attached to the charge. Use strings, numbers, or booleans only.
Show Example
Show Example
{
"order_id": "123",
"source": "web_checkout"
}
boolean
default:"false"
When
true, returns a fee breakdown without creating an actual charge. Use this to show customers exactly what they’ll pay before confirming.Response
{
"status": true,
"message": "Payment charge created successfully",
"data": {
"token": "eyJtZXJjaGFudF9pZCI6MTc5MCwi...",
"charge_id": "intent_abc123-def456-789",
"amount": 5320,
"currency": "USD",
"status": "pending",
"mode": "live",
"breakdown": {
"subtotal": 5000,
"transaction_fee": 320,
"total": 5320,
"customer_pays_fees": true,
"is_international": false
},
"expires_at": "2024-01-15T10:30:00.000Z"
}
}
Response Fields
| Field | Description |
|---|---|
token | Signed token for <KhaimeCheckout />. Contains all gateway configuration. Expires in 15 minutes. |
charge_id | Unique charge identifier for tracking |
amount | Final amount to charge in smallest currency unit |
currency | Currency code |
status | Payment status (pending) |
mode | Environment (live or sandbox) |
breakdown | Fee breakdown with subtotal, transaction_fee, total, customer_pays_fees, is_international |
expires_at | Token expiration timestamp |
Token Expiration: Tokens expire after 15 minutes. If a customer waits too long, create a new payment intent.
Accepting Payment
Option 1: Embedded Checkout (Recommended)
Use@khaime/react to embed checkout directly in your app:
1
Install the SDK
npm install @khaime/react
yarn add @khaime/react
pnpm add @khaime/react
2
Render the Checkout
import { KhaimeCheckout } from '@khaime/react';
function PaymentPage({ paymentToken }) {
return (
<KhaimeCheckout
token={paymentToken}
onSuccess={(result) => {
// Payment successful - redirect to confirmation
window.location.href = `/order/${result.transaction_id}`;
}}
onError={(error) => {
console.error('Payment failed:', error.message);
}}
onClose={() => {
// User closed checkout without completing
}}
/>
);
}
- Detects the payment gateway from the token
- Renders the appropriate payment UI (card form, mobile money, etc.)
- Handles 3D Secure authentication
- Manages loading states and errors
Option 2: Redirect Checkout
Redirect the customer to Khaime’s hosted checkout:// After getting the response
window.location.href = response.data.payment_url;
callback_url.
Currency & Gateway Routing
Khaime automatically selects the optimal payment gateway based on currency:| Currency | Gateway | Payment UI |
|---|---|---|
| USD, EUR, GBP, CAD, AUD | Stripe | Embedded card form |
| NGN | Paystack | Popup / redirect |
| GHS, KES, ZAR, TZS, UGX | Flutterwave / StartButton | Redirect |
Examples
Same Currency (USD → USD)
curl -X POST https://api.khaime.com/api/v1/payment/intent \
-H "X-API-Key: pk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"charge_amount": 5000,
"charge_currency": "USD",
"merchant_amount": 5000,
"merchant_currency": "USD",
"description": "Order #123",
"reference": "order_123",
"callback_url": "https://yourstore.com/order-complete",
"customer": {
"email": "customer@example.com",
"first_name": "John",
"last_name": "Doe",
"country": "US"
}
}'
Multicurrency (Merchant prices USD, Customer pays NGN)
When the customer pays in a different currency than the merchant’s settlement currency:- Get converted amount from
/pricing/calculate - Pass both amounts —
merchant_amount/merchant_currency(what you receive) andcharge_amount/charge_currency(what customer pays) - Khaime validates the conversion matches within 0.02% tolerance
# Step 1: Get converted amount
# GET /pricing/calculate?amount=9000&source_currency=USD&target_currency=NGN
# Response: { "converted_amount": 1440000, "rate": 1600 }
# Step 2: Create payment intent with both amounts
curl -X POST https://api.khaime.com/api/v1/payment/intent \
-H "X-API-Key: pk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"charge_amount": 1440000,
"charge_currency": "NGN",
"merchant_amount": 9000,
"merchant_currency": "USD",
"description": "Design consultation",
"customer": {
"email": "client@example.ng",
"first_name": "Emeka",
"last_name": "Eze",
"country": "NG"
}
}'
Settlement guarantee: The merchant receives
merchant_amount in merchant_currency regardless of exchange rate fluctuations. Khaime absorbs the FX risk.Preview Mode (Fee Breakdown)
curl -X POST https://api.khaime.com/api/v1/payment/intent \
-H "X-API-Key: pk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"preview": true,
"charge_amount": 5000,
"charge_currency": "USD",
"merchant_amount": 5000,
"merchant_currency": "USD",
"customer": {
"email": "customer@example.com"
}
}'
{
"status": true,
"message": "Preview calculated",
"data": {
"preview": true,
"amount": 5320,
"currency": "USD",
"breakdown": {
"subtotal": 5000,
"transaction_fee": 320,
"total": 5320,
"customer_pays_fees": true,
"is_international": false
}
}
}
Full Backend Example (Next.js)
// app/api/checkout/route.ts
import { NextResponse } from 'next/server';
const KHAIME_API_KEY = process.env.KHAIME_API_KEY!;
const KHAIME_API_URL = 'https://api.khaime.com/api/v1';
export async function POST(request: Request) {
const { amount, currency, customer, orderId } = await request.json();
const response = await fetch(`${KHAIME_API_URL}/payment/intent`, {
method: 'POST',
headers: {
'X-API-Key': KHAIME_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
// Customer pays this amount in this currency
charge_amount: amount,
charge_currency: currency,
// Merchant receives this amount in this currency
merchant_amount: amount,
merchant_currency: currency,
description: `Order #${orderId}`,
reference: `order_${orderId}`,
callback_url: `${process.env.NEXT_PUBLIC_URL}/order/${orderId}`,
customer,
}),
});
const data = await response.json();
if (!data.status) {
return NextResponse.json({ error: data.message }, { status: 400 });
}
// Return only the token to the frontend
return NextResponse.json({
token: data.data.token,
amount: data.data.amount,
currency: data.data.currency,
});
}
Error Codes
| Status | Error Code | Fix |
|---|---|---|
400 | VALIDATION_MISSING_FIELD | Include required fields: amount, currency, customer.email |
400 | PAYMENT_AMOUNT_MISMATCH | Use /pricing/calculate for currency conversion |
400 | PAYMENT_CURRENCY_UNSUPPORTED | Use a supported currency |
401 | — | Check your X-API-Key header |
Confirming Payment
Always verify payments via webhooks before fulfilling orders. Frontend callbacks are for UI purposes only — a malicious user could fake them.
payment.succeeded webhook events to confirm payment and fulfill orders.
Related
React SDK
Full
<KhaimeCheckout /> documentationWebhooks
Listen for payment events
Gateway Routing
How currency determines gateway
Subscriptions
Recurring payments
