Get API keys
Create a test key and configure your merchant integration.
- Create a merchant account, confirm your email and log in.
- Open Dashboard → API & Webhooks (
/dashboard/connect). Create a test API key (kp_test_…), which is shown only when issued. Store it in a server-side environment variable. Never put it in JavaScript sent to browsers, mobile apps, URLs, support tickets or Git. - Configure payment methods under
/dashboard/methodsand confirm which providers are available. A configured provider may still be unavailable for a specific amount or customer. - Optionally verify your store's HTTPS domain under
/dashboard/connect; verification is required before sending a customreturn_url. - Create a secure HTTPS webhook destination under
/dashboard/connectand store its one-timewhsec_…secret. Never use the payment-provider callback URLs as your merchant webhook endpoint. - When real processing has been approved for your route, issue a separate
kp_live_…key. A test key cannot charge customers or produce a completed live payment.
Every merchant request uses these headers:
Authorization: Bearer kp_test_YOUR_PRIVATE_KEY
Content-Type: application/json
| Environment | Key prefix | Purpose |
|---|---|---|
| Test | kp_test_ |
Safely build and test API behavior, no real payment collection |
| Live | kp_live_ |
Real processing only where approved, configured and available |
Merchant keys include payments:create and payments:read permissions. Requests and payment status queries are bound to the owning merchant and test/live environment. Protect all keys as passwords.
Create a payment
Generate an order-safe checkout link from your backend.
/api/v1/paymentsauthenticated · JSON · success 201 Created (first creation)curl --request POST 'https://kovirapay.com/api/v1/payments' \
--header 'Authorization: Bearer kp_test_REPLACE_WITH_YOUR_KEY' \
--header 'Content-Type: application/json' \
--data '{
"order_id": "ORDER-1042",
"idempotency_key": "shop-order-1042-v1",
"amount": "49.99",
"currency": "EUR",
"checkout_mode": "hosted"
}'<?php
// Run on your own server, never in browser JavaScript.
$key = getenv('KOVIRAPAY_API_KEY');
$payload = [
'order_id' => 'ORDER-1042',
'idempotency_key' => 'shop-order-1042-v1',
'amount' => '49.99',
'currency' => 'EUR',
'checkout_mode' => 'hosted'
];
$ch = curl_init('https://kovirapay.com/api/v1/payments');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $key,
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR)
]);
$response = curl_exec($ch);
if ($response === false) throw new RuntimeException(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
if ($status >= 400) throw new RuntimeException('Payment creation failed: HTTP ' . $status);
// Save $result['payment_id']; redirect to $result['checkout_url'].
// Node.js 20+ — server side only.
const key = process.env.KOVIRAPAY_API_KEY;
if (!key) throw new Error('Missing API key');
const response = await fetch('https://kovirapay.com/api/v1/payments', {
method: 'POST',
headers: {
Authorization: `Bearer ${key}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
order_id: 'ORDER-1042',
idempotency_key: 'shop-order-1042-v1',
amount: '49.99',
currency: 'EUR',
checkout_mode: 'hosted'
})
});
const payment = await response.json();
if (!response.ok) throw new Error(payment.error || `HTTP ${response.status}`);
// Save payment.payment_id; redirect to payment.checkout_url.
You may optionally add customer_email when your checkout needs it. Keep buyer details out of logs and code samples.
| Field | Required | Meaning |
|---|---|---|
order_id |
Yes | Your own unique order reference, 1–150 characters; displayed to the customer |
idempotency_key |
Yes | Stable unique string for this intended charge, 8–190 characters; letters, digits, dot, underscore, colon and hyphen |
amount |
Yes | Positive decimal string in invoice currency, two decimal places (e.g. "49.99") |
currency |
No | Three-letter invoice currency; defaults to USD. Supported currencies require a usable FX rate. |
checkout_mode |
No | "hosted" (default) or "direct" |
payment_method |
For direct only | One of credit_card, paypal, ideal, cash_app, crypto, venmo, revolut, klarna, upi_imps, interac (subject to merchant/provider availability). UPI / IMPS requires an INR invoice, Interac requires CAD. |
customer_email |
No | Valid purchaser email, when supplied |
return_url |
No | HTTPS page on your verified domain; the customer may be redirected here but it does not confirm payment |
Idempotency: Reuse exactly the same key and exact payment data when retrying after a timeout; the existing payment is returned rather than creating a duplicate. Changing the amount, order, method, email, environment or return URL for an existing key is rejected. Create a new key for a genuinely new payment attempt.
Understand the response
Read the IDs, amounts and URLs returned by KoviraPay.
Illustrative shape (IDs and checkout token are examples; the server generates real ones):
{
"payment_id": "0123456789abcdef0123456789abcdef",
"order_id": "ORDER-1042",
"status": "created",
"settlement_status": "pending",
"amount": "49.99",
"currency": "EUR",
"usd_amount": "54.99",
"payment_method": null,
"test_mode": true,
"checkout_url": "https://kovirapay.com/checkout/EXAMPLE_TOKEN"
}
The response's usd_amount is generated from your configured FX rate, not a fixed value. The shown 54.99 is an illustrative placeholder. Save payment_id against your order, then send the customer's browser to the returned checkout_url (never construct one from a guessed token).
// Executed on your own shop server, after validating the order:
header('Location: ' . $payment['checkout_url'], true, 303);
exit;
The customer sees available payment methods and is redirected to an independent provider where appropriate. Provider fees, KYC/KYB, availability and network fees are separate from KoviraPay's advertised 1% platform fee.
Available methods
Discover eligible payment methods for an amount and currency.
/api/v1/methods?amount=49.99¤cy=EURauthenticated (scope payments:read )curl 'https://kovirapay.com/api/v1/methods?amount=49.99¤cy=EUR' \
-H 'Authorization: Bearer kp_test_REPLACE_WITH_YOUR_KEY'
Response includes methods, each with an id, label and currently eligible providers list. Providers include ID, display name and any available verification note/link. A method may be omitted if disabled, stale, below the provider minimum or not operational. This endpoint describes current configuration, not a guaranteed successful payment.
The implementation's invoice currencies are: USD, EUR, GBP, PHP, CAD, AUD, CHF, SGD, NZD, INR, AED, BRL, MXN, ZAR, PLN, SEK, NOK, DKK, CZK, RON, HUF. Live availability still depends on FX freshness, amount, provider and merchant setup.
Direct checkout
Skip method selection when you need an approved direct route.
Use this only if you want to choose the method yourself instead of showing KoviraPay hosted method selection.
Step A: Create a payment using the same POST /api/v1/payments, but set:
{
"order_id": "ORDER-1043",
"idempotency_key": "shop-order-1043-v1",
"amount": "49.99",
"currency": "EUR",
"checkout_mode": "direct",
"payment_method": "credit_card"
}
/api/v1/payments/startStep B: Start that payment using :curl -X POST 'https://kovirapay.com/api/v1/payments/start' \
-H 'Authorization: Bearer kp_test_REPLACE_WITH_YOUR_KEY' \
-H 'Content-Type: application/json' \
--data '{"payment_id":"0123456789abcdef0123456789abcdef","payment_method":"credit_card"}'
The JSON response includes payment_id and redirect_url. Redirect the browser to the returned URL. For hosted payments, do not call /payments/start; the hosted page manages selection. An optional provider_id is accepted at the start step but must be allowed by the merchant's configured routing policy.
Check payment status
Verify authenticated payment and settlement state.
status = completed + settlement_status = confirmed + test_mode = false and exact order/amount/currency match./api/v1/payments/{payment_id}authenticated (scope payments:read )curl 'https://kovirapay.com/api/v1/payments/0123456789abcdef0123456789abcdef' \
-H 'Authorization: Bearer kp_test_REPLACE_WITH_YOUR_KEY'
The response uses the same documented payment fields. Your backend must require all of the following before considering a live order paid:
status === "completed"settlement_status === "confirmed"test_mode === false- Matching
order_id,payment_id, originalamountandcurrency
created, pending, processing, review, verification_pending, and unknown states are not successful payments. Some are provider or chain verification steps; retain the order as awaiting payment or manual review. There is no frontend-only fulfillment shortcut.
Verify webhooks
Authenticate events, handle retries and prevent double fulfillment.
Configure your delivery destination in Dashboard → API & Webhooks. KoviraPay sends HTTP POST requests with an exact raw JSON body and these headers:
X-Kovira-Event-ID: 0123456789abcdef0123456789abcdef
X-Kovira-Timestamp: 1792180000
X-Kovira-Signature: v1=YOUR_LOWERCASE_HMAC_SHA256
Content-Type: application/json
The timestamp shown is illustrative. Calculate:
v1=HEX_HMAC_SHA256(
webhook_endpoint_secret,
TIMESTAMP + "." + EVENT_ID + "." + EXACT_RAW_BODY
)
Use your webhook endpoint signing secret (whsec_…), not your API key. Do not JSON-reencode before verifying, trim whitespace or replace line endings: the original bytes matter. Reject events older than 5 minutes, malformed signatures and event IDs you have already processed. Save the event ID durably and return HTTP 2xx only after the processing transaction commits.
Example event (the actual server emits its own IDs/timestamps):
{
"id": "0123456789abcdef0123456789abcdef",
"type": "payment.completed",
"created_at": "2026-10-16T10:00:00+00:00",
"data": {
"payment_id": "fedcba9876543210fedcba9876543210",
"order_id": "ORDER-1042",
"status": "completed",
"amount": "49.99",
"currency": "EUR",
"usd_amount": "54.99",
"settlement_status": "confirmed",
"test_mode": false
}
}
Events use types such as payment.completed and payment.review. A webhook.test event is only a connectivity test and must never complete an order. Ignore unrecognized event types safely. On failed delivery the existing worker retries using the same event ID, up to 8 attempts with increasing delays; your handler must be idempotent.
PHP webhook receiver
Secure signature verification example for your PHP backend.
A minimal secure validation example. Replace the TODO with a real transactional database update and check invoice matching; do not mark an order paid before those checks. Configure this script behind HTTPS and supply KOVIRAPAY_WEBHOOK_SECRET in server configuration, not the webroot.
View full PHP webhook receiver
<?php
declare(strict_types=1);
$secret = getenv('KOVIRAPAY_WEBHOOK_SECRET') ?: '';
$body = file_get_contents('php://input');
$id = (string)($_SERVER['HTTP_X_KOVIRA_EVENT_ID'] ?? '');
$timestamp = (string)($_SERVER['HTTP_X_KOVIRA_TIMESTAMP'] ?? '');
$received = (string)($_SERVER['HTTP_X_KOVIRA_SIGNATURE'] ?? '');
if ($secret === '' || !is_string($body) || strlen($body) > 65536 ||
!preg_match('/^[a-f0-9]{32}$/D', $id) ||
!preg_match('/^[0-9]{10}$/D', $timestamp) ||
abs(time() - (int)$timestamp) > 300 ||
!preg_match('/^v1=[a-f0-9]{64}$/D', $received)) {
http_response_code(400);
exit;
}
$expected = 'v1=' . hash_hmac('sha256',
$timestamp . '.' . $id . '.' . $body, $secret);
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
if (!is_array($event) || ($event['id'] ?? '') !== $id) {
http_response_code(400);
exit;
}
// TODO: Start a database transaction and INSERT $id into a table with
// a UNIQUE event_id constraint. If already present, safely return 200.
// Match the payment ID, order ID, amount, currency and test/live mode
// to the original shop order. Only for payment.completed with
// status=completed AND settlement_status=confirmed AND test_mode=false
// should your order be marked paid. Commit before acknowledging.
http_response_code(200);
echo 'ok';
Important: The code above validates signature and payload format but deliberately does not include a fictitious paid-order implementation. Without the TODO transaction implemented it merely acknowledges events and must not be deployed as your final fulfillment handler.
Node.js integration
Backend integration example using native Node.js fetch.
Server-side Node.js 20+, not browser-side JavaScript:
const key = process.env.KOVIRAPAY_API_KEY;
if (!key) throw new Error('Missing server-side KOVIRAPAY_API_KEY');
const response = await fetch('https://kovirapay.com/api/v1/payments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${key}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
order_id: 'ORDER-1042',
idempotency_key: 'shop-order-1042-v1',
amount: '49.99',
currency: 'EUR',
checkout_mode: 'hosted'
})
});
const payment = await response.json();
if (!response.ok) throw new Error(payment.error ?? `HTTP ${response.status}`);
// Save payment.payment_id against your validated shop order on the server.
// Return only payment.checkout_url to the browser to redirect the customer.
console.log(payment.checkout_url);
Never call the KoviraPay API with a private key from React, Vue, a WordPress theme, frontend JavaScript or a mobile app bundle. Make the call from your backend.
WooCommerce
Use Quick Connect and the official store plugin.
For WooCommerce, install the latest published plugin from your merchant dashboard. Generate a one-time Quick Connect code in /dashboard/connect, paste it into the plugin and complete the shop ownership challenge. Codes expire after five minutes and can be used only once; the exchange generates a scoped API key and webhook secret. The connection also verifies your shop domain for return URLs. Full WooCommerce setup guide.
/api/v1/connect/exchangeThere is a server-to-server endpoint , used by the official plugin. It verifies shop ownership and should not be cReseller API
Delegated payments, merchant permissions and replay protection.
An approved reseller can invite merchants and create delegated payments only after each merchant independently authorizes access and limits. A reseller may not override a merchant wallet or bypass risk controls.
| Endpoint | Purpose |
|---|---|
POST /api/v1/reseller/invitations |
Invite a business using email, business_name, unique client_ref |
GET /api/v1/reseller/merchants |
List connected merchants and authorization status |
POST /api/v1/reseller/payments |
Create a delegated payment (merchant_id, unique client_ref, order_id, amount, currency) |
POST /api/v1/reseller/payments/start |
Start an authorized direct-mode delegated payment |
GET /api/v1/reseller/payments?merchant_id=123 |
List this reseller's payments for an authorized merchant |
GET /api/v1/reseller/payments/{payment_id} |
Retrieve an individual delegated payment |
Use reseller keys kr_test_… or kr_live_…, not merchant keys. In addition to Authorization: Bearer …, every live reseller HTTP request requires X-Kovira-Timestamp, X-Kovira-Nonce and X-Kovira-Signature. The signature is a lowercase raw HMAC SHA-256 hex (no v1= prefix), calculated with the reseller API key itself as secret over these five newline-delimited lines:
UPPERCASE_HTTP_METHOD
EXACT_PATH_WITH_QUERY
UNIX_TIMESTAMP
FRESH_NONCE
LOWERCASE_SHA256_OF_EXACT_REQUEST_BODY
Sample PHP server-side signer:
$key = getenv('KOVIRAPAY_RESELLER_KEY');
$method = 'POST';
$path = '/api/v1/reseller/payments';
$body = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
$timestamp = (string)time();
$nonce = bin2hex(random_bytes(16));
$toSign = $method."\n".$path."\n".$timestamp."\n".$nonce."\n".hash('sha256', $body);
$signature = hash_hmac('sha256', $toSign, $key);
// Send EXACT same $body, along with Bearer $key and three X-Kovira headers.
Timestamp must be within 5 minutes; the nonce must be fresh, 16–80 permitted characters and is reserved for 10 minutes. Reusing the nonce returns HTTP 409. On network retry use a new nonce but preserve the same logical client_ref. Live reseller processing also requires explicit audited rollout; reseller approval alone does not activate it. See the bundled docs/RESELLER_API_V07_DE.md for the detailed operational model.
Errors & troubleshooting
What an HTTP error means and the next step to take.
| HTTP code | Typical meaning | Next step |
|---|---|---|
400 |
Invalid JSON | Send valid JSON, valid Content-Type; do not rely on HTML forms |
401 |
Missing, invalid, revoked or expired credentials | Check key prefix, value and Authorization header; never log the key |
403 |
Scope, environment, live policy or reseller authorization denied | Check permissions and live readiness; do not retry with bypass fields |
404 |
Payment not found or not owned by this merchant | Verify payment_id and key environment |
409 |
Payment conflict, invalid hosted/direct start or replayed reseller nonce | Check idempotency, mode and nonce handling |
413 |
Request body too large | Reduce JSON payload; payment API request bodies are limited to 16 KiB |
422 |
Validation failed (currency, amount, key, method, verified return URL or policy) | Read the returned error message; fix request data |
429 |
Rate limit | Apply backoff, do not flood the endpoint |
500 |
Unexpected server error | Preserve request context without secrets, check server logs, contact support |
Many validation responses are {"error":"description"}. Do not hard-code user-facing copy to any single error string. A 5xx or network timeout is not proof that a payment creation failed; retry with the same idempotency key.
Frequent issues:
- No payment methods shown: method disabled, provider down, minimum too high, stale FX, unavailable route or pending approval.
- Return URL rejected: register and verify the exact HTTPS shop hostname in merchant settings.
- Webhook signature invalid: sign the raw bytes, use the endpoint's signing secret, keep server clocks synchronized.
- Webhook delivery pending: inspect your endpoint HTTPS certificate, HTTP status, server logs and KoviraPay retry monitor.
- Payment processing for a long time: provider callbacks and chain settlement are separate; verify status through the authenticated API and investigate in the merchant dashboard.
- Test payment not completed: expected—test keys do not complete real charges.
Go-live checklist
A practical final checklist before accepting real payments.
- Business and payment route approved for live processing; payment methods are actually available.
- Separate test/live keys securely stored outside source code; least privilege; revocation procedure established.
- HTTPS configured and your shop domain verified (if using
return_url). - Stable order-specific idempotency keys and server-side
payment_idmapping. - Webhook signature, timestamp, event deduplication, amount and environment verified before any fulfillment.
- Treat only
completedandconfirmedin live mode as paid; keep provider/chain review states pending. - Duplicate, delayed, failed and out-of-order events tested.
- Worker running; retries and reconciliation monitored.
- Refunds, customer support, fraud response, accounting and provider-specific obligations documented.
Need help? Visit Support, Contact, or review Pricing, Service Role & Fund Flow, Terms and Privacy information in Terms.
Start building with KoviraPay.
Generate your test API key, connect your store, then enable live processing when your payment routes are approved.