Authentication
Server-to-server API requests require your merchant credentials passed as HTTP headers. Generate these from the API Keys page in your merchant portal.
X-API-Key: pk_live_your_api_key
X-API-Secret: sk_live_your_api_secret
Content-Type: application/json
Security: Never expose your API Secret in client-side code, mobile apps, or public repositories. Use it only from your backend server. Our CORS policy does not accept X-API-Secret on cross-origin requests from a browser — if your frontend needs to initiate a payment, create a hosted checkout session from your backend and redirect the customer.
Using the checkout widget? Before the widget can call our API from your site, you must register your site's origin(s) in Settings → Allowed Origins. See the Allowed Origins guide.
Allowed Origins (CORS)
Browsers enforce a same-origin policy. When the checkout widget is embedded on your site, the browser sends an Origin header on every API call, and will block the response unless we echo that origin back in Access-Control-Allow-Origin.
For security, we only reflect origins that you have registered against your merchant account. Any origin that isn't on your list is rejected at the CORS layer — even if a valid API key is presented.
Register your origin(s)
Add the exact HTTPS origins of the sites that will host the widget. Use the portal (Settings → Allowed Origins) or call the API directly:
// Request
{
"origins": [
"https://www.tfcfunder.com",
"https://checkout.tfcfunder.com"
]
}Rules
- Must be
https:// (plain http:// is only accepted for localhost). - Scheme + host + optional port only — no paths, queries, or fragments. e.g.
https://shop.example.com:8443 is fine; https://shop.example.com/checkout is not. - Each subdomain is distinct.
https://example.com does not cover https://www.example.com — register both if needed. - Maximum 10 origins per merchant.
- Changes take effect within ~60 seconds.
Allowed CORS headers
Cross-origin callers may send these request headers:
Authorization, Content-Type, X-API-Key, X-Requested-With, X-Idempotency-Key
Note:X-API-Secret is deliberately not on this list. A browser cannot send it cross-origin, by design — it forces integrations that need payment initiation from the frontend to go through a hosted checkout session created server-side. This protects your secret from being leaked via view-source or browser extensions.
Troubleshooting
| Symptom | Likely cause |
|---|
Browser console: has been blocked by CORS policy | The page's origin isn't in your allowlist, or the origin string doesn't match exactly (scheme/host/port). |
Preflight OPTIONS returns 403 | Origin not registered. Confirm the exact Origin header in DevTools Network tab. |
Works on localhost, fails in production | You registered http://localhost:4200 but not your production origin. |
| Changes don't take effect | Registry refreshes every 60s. Wait a minute, or re-save to trigger an immediate refresh. |
Checkout Widget
The fastest way to start accepting payments. Add our pre-built, customizable checkout UI to any website.
Before you embed: register the site's URL under Settings → Allowed Origins, otherwise the browser will block the widget's API calls. See Allowed Origins (CORS).
<!-- Add before </body> -->
<script src="https://cdn.jsdelivr.net/npm/@finivex/payment-checkout@1.0.0/dist/checkout.iife.js"></script>
npm install @finivex/payment-checkout
import { checkout } from '@finivex/payment-checkout';Step 1 — Mint a session token on your backend
Your server calls POST /v1/payments/widget-session with X-API-Key + X-API-Secret. The response contains a short-lived sessionToken bound to this transaction — safe to hand to the browser.
const sess = await fetch('https://gateway.finivex.online/api/pg/v1/payments/widget-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'pk_live_your_api_key',
'X-API-Secret': 'sk_live_your_api_secret',
},
body: JSON.stringify({
transactionId: 'order_abc123',
amount: 25.00,
currency: 'USD',
}),
}).then(r => r.json());
// Return sess.data.sessionToken to your frontend.Step 2 — Open the widget with the session token
PayGateway.checkout({
// Required
apiKey: 'pk_live_your_api_key',
sessionToken: 'eyJhbGciOiJIUzUxMiJ9...', // from step 1
apiUrl: 'https://gateway.finivex.online/api/pg',
amount: 25.00,
currency: 'USD',
transactionId: 'order_abc123',
// Optional
merchantName: 'My Store',
description: 'Payment for Order #123',
customerPhone: '263771234567',
customerEmail: 'customer@example.com',
callbackUrl: 'https://mystore.com/webhook',
// Restrict methods (optional)
methods: ['ECOCASH', 'VISA', 'MASTERCARD'],
// Callbacks
onSuccess: (result) => {
console.log('Payment complete!', result);
},
onError: (error) => {
console.error('Payment failed:', error);
},
onCancel: () => {
console.log('User cancelled');
}
});Theming
theme: {
primaryColor: '#4F46E5', // Your brand color
fontFamily: 'Inter, sans-serif',
borderRadius: '12px'
}Hosted Checkout Page
Redirect your customers to a Finivex-hosted checkout page. No frontend code required — just call the API from your backend and redirect.
How It Works
1.Your backend calls POST /v1/payments/hosted-checkout to create a checkout session.
2.The response contains a redirectUrl. Redirect your customer to this URL.
3.The customer selects a payment method and completes payment on the hosted page.
4.Poll GET /v1/payments/status or listen for a webhook to confirm payment.
Step 1 — Create Checkout Session
Call this from your backend server (never from client-side code).
// Request
{
"transactionId": "order_abc123",
"amount": 25.00,
"currency": "USD",
"description": "Payment for Order #123",
"returnUrl": "https://mystore.com/orders/abc123/success",
"cancelUrl": "https://mystore.com/orders/abc123/cancel"
}
// Response
{
"success": true,
"data": {
"transactionRef": "order_abc123",
"orderId": "CHK-92837465",
"message": "Checkout session created",
"currency": "USD",
"amount": 25.00,
"redirectUrl": "https://checkout.finivex.co.zw?ref=CHK-92837465"
}
}Step 2 — Redirect the Customer
const response = await fetch('https://gateway.finivex.online/api/pg/v1/payments/hosted-checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'pk_live_your_api_key',
'X-API-Secret': 'sk_live_your_api_secret'
},
body: JSON.stringify({
transactionId: 'order_abc123',
amount: 25.00,
currency: 'USD',
description: 'Payment for Order #123',
returnUrl: 'https://mystore.com/orders/abc123/success',
cancelUrl: 'https://mystore.com/orders/abc123/cancel'
})
});
const { data } = await response.json();
// Redirect the customer to the hosted checkout page
res.redirect(data.redirectUrl);import requests
response = requests.post(
'https://gateway.finivex.online/api/pg/v1/payments/hosted-checkout',
headers={
'X-API-Key': 'pk_live_your_api_key',
'X-API-Secret': 'sk_live_your_api_secret',
},
json={
'transactionId': 'order_abc123',
'amount': 25.00,
'currency': 'USD',
'description': 'Payment for Order #123',
'returnUrl': 'https://mystore.com/orders/abc123/success',
'cancelUrl': 'https://mystore.com/orders/abc123/cancel',
}
)
data = response.json()['data']
# Redirect the customer to the hosted checkout page
return redirect(data['redirectUrl'])$response = json_decode(file_get_contents(
'https://gateway.finivex.online/api/pg/v1/payments/hosted-checkout',
false,
stream_context_create(['http' => [
'method' => 'POST',
'header' => implode("\r\n", [
'Content-Type: application/json',
'X-API-Key: pk_live_your_api_key',
'X-API-Secret: sk_live_your_api_secret',
]),
'content' => json_encode([
'transactionId' => 'order_abc123',
'amount' => 25.00,
'currency' => 'USD',
'description' => 'Payment for Order #123',
'returnUrl' => 'https://mystore.com/orders/abc123/success',
'cancelUrl' => 'https://mystore.com/orders/abc123/cancel',
]),
]])
));
// Redirect the customer to the hosted checkout page
header('Location: ' . $response->data->redirectUrl);
exit;curl -X POST https://gateway.finivex.online/api/pg/v1/payments/hosted-checkout \
-H "Content-Type: application/json" \
-H "X-API-Key: pk_live_your_api_key" \
-H "X-API-Secret: sk_live_your_api_secret" \
-d '{
"transactionId": "order_abc123",
"amount": 25.00,
"currency": "USD",
"description": "Payment for Order #123",
"returnUrl": "https://mystore.com/orders/abc123/success",
"cancelUrl": "https://mystore.com/orders/abc123/cancel"
}'Step 3 — Confirm Payment
After the customer pays, verify the status from your backend.
// GET /v1/payments/status?transactionId=order_abc123
{
"success": true,
"data": {
"transactionId": "order_abc123",
"status": "COMPLETED",
"amount": 25.00,
"currency": "USD",
"paymentMethod": "ECOCASH",
"processedAt": "2026-03-24T10:30:05"
}
}Tip: Always verify the payment status server-side before fulfilling an order. Don't rely solely on the redirect — use the status endpoint or webhooks as the source of truth.
Payment Links
Create a shareable URL that a customer can open in a browser to pay via the hosted checkout page. Great for invoices, remote sales, chat/WhatsApp payments, and one-off requests — no frontend integration required.
How It Works
1.Your backend calls POST /v1/payments/payment-link with the amount, currency, and (optionally) a customer email.
2.The response contains a paymentLink URL. Send it to the customer (SMS, WhatsApp, email). If customerEmail is supplied, the gateway also emails the link automatically.
3.The customer opens the link, picks a payment method, and pays on the hosted checkout page.
4.On success, the link flips to PAID, the gateway fires your configured webhook, and (if email was supplied) sends a receipt.
Step 1 — Create the Link
Call this from your backend server.
// Request
{
"amount": 25.00,
"currency": "USD",
"description": "Invoice #INV-0042",
"customerEmail": "customer@example.com",
"customerPhone": "263771234567",
"redirectUrl": "https://mystore.com/orders/42",
"expiresInMinutes": 60
}
// Response
{
"success": true,
"data": {
"reference": "b1c0f4b2-8a4a-4a0e-8f1a-9a2c1e5f7b10",
"paymentLink": "https://checkout.finivex.co.zw/link?ref=b1c0f4b2-8a4a-4a0e-8f1a-9a2c1e5f7b10",
"amount": 25.00,
"currency": "USD",
"status": "ACTIVE",
"expiresAt": "2026-04-13T21:30:00",
"createdAt": "2026-04-13T20:30:00",
"merchantName": "Acme Corp",
"redirectUrl": "https://mystore.com/orders/42"
}
}Request Fields
| Field | Type | | Description |
|---|
| amount | number | required | Amount to charge. Must be > 0. |
| currency | "USD" | "ZWG" | required | Payment currency. |
| description | string | optional | Shown to the customer on the hosted page and in the email. |
| customerEmail | string | optional | If set, the gateway emails the link to this address. |
| customerPhone | string | optional | Pre-fills the phone number on the hosted checkout page. |
| redirectUrl | string | optional | Where to send the customer after a successful payment. |
| expiresInMinutes | integer | optional | Link lifetime. Defaults to 1440 (24 hours). |
Step 2 — Send the Link to the Customer
curl -X POST https://gateway.finivex.online/api/pg/v1/payments/payment-link \
-H "Content-Type: application/json" \
-H "X-API-Key: pk_live_your_api_key" \
-H "X-API-Secret: sk_live_your_api_secret" \
-d '{
"amount": 25.00,
"currency": "USD",
"description": "Invoice #INV-0042",
"customerEmail": "customer@example.com",
"redirectUrl": "https://mystore.com/orders/42"
}'const res = await fetch('https://gateway.finivex.online/api/pg/v1/payments/payment-link', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'pk_live_your_api_key',
'X-API-Secret': 'sk_live_your_api_secret'
},
body: JSON.stringify({
amount: 25.00,
currency: 'USD',
description: 'Invoice #INV-0042',
customerEmail: 'customer@example.com',
redirectUrl: 'https://mystore.com/orders/42'
})
});
const { data } = await res.json();
// Share data.paymentLink with the customer (SMS, WhatsApp, email, etc.)
sendWhatsApp(customer.phone, `Pay here: ${data.paymentLink}`);import requests
res = requests.post(
'https://gateway.finivex.online/api/pg/v1/payments/payment-link',
headers={
'X-API-Key': 'pk_live_your_api_key',
'X-API-Secret': 'sk_live_your_api_secret',
},
json={
'amount': 25.00,
'currency': 'USD',
'description': 'Invoice #INV-0042',
'customerEmail': 'customer@example.com',
'redirectUrl': 'https://mystore.com/orders/42',
}
)
data = res.json()['data']
# Share data['paymentLink'] with the customer
print(data['paymentLink'])Link Lifecycle
A payment link always has one of the following statuses:
| Status | Description |
|---|
| ACTIVE | Link is live and payable. |
| PAID | Customer paid successfully. The link cannot be reused. |
| EXPIRED | Link passed its expiresAt without being paid. |
| CANCELLED | Merchant cancelled the link before it was paid. |
Managing Links
| Method | Path | Purpose |
|---|
| GET | /v1/payments/payment-link | List the merchant's payment links (paginated). |
| DELETE | /v1/payments/payment-link/{reference} | Cancel an ACTIVE link. |
Note: A link can only be paid once. Listen for the webhook (or poll the link status) to know when payment is complete — don't rely on the redirectUrl landing alone, as the customer may close their browser before returning.
Configuration Reference
All available options for the checkout widget.
| Parameter | Type | | Description |
|---|
| apiKey | string | required | Your public API key (pk_live_…). Safe to embed in browser code. |
| sessionToken | string | required | Short-lived JWT minted server-side via POST /v1/payments/widget-session. Bound to transactionId + amount + currency. |
| apiSecret | string | legacy | Deprecated. Same-origin only (e.g. Finivex-hosted checkout). Do not ship to merchant browsers — use sessionToken. |
| apiUrl | string | required | Payment gateway API base URL. |
| amount | number | required | Amount to charge (e.g. 25.00). |
| currency | "USD" | "ZWG" | required | Payment currency. |
| transactionId | string | required | Your unique order/transaction ID. |
| merchantName | string | optional | Displayed in the checkout header. |
| description | string | optional | Payment description shown to customer. |
| customerPhone | string | optional | Pre-fill phone for mobile money (263...). |
| customerEmail | string | optional | Customer email for receipts. |
| callbackUrl | string | optional | Webhook URL for payment notifications. |
| methods | string[] | optional | Restrict to specific methods. |
| theme | object | optional | Custom colors, fonts, border radius. |
| onSuccess | function | optional | Callback on successful payment. |
| onError | function | optional | Callback on payment failure. |
| onCancel | function | optional | Callback when customer cancels. |
| onStatusChange | function | optional | Called on every status poll update. |
API Endpoints
Use these REST endpoints directly if you want to build your own custom checkout UI.
Base URL: https://gateway.finivex.online/api/pg
Create the transaction and initiate the payment in a single call. For mobile money (EcoCash, OneMoney, Omari, InnBucks) this immediately pushes the approval prompt to the customer's phone. Authenticate with a short-lived widget session token — mint it server-side via POST /v1/payments/widget-session (using your X-API-Key + X-API-Secret) for the same reference, amount and currency, then send it as Authorization: Bearer <sessionToken>. Your API secret never touches the client. The response status is usually PROCESSING until the customer approves — poll GET /v1/charges/{reference} or listen for a webhook for the final outcome.
Request Body
{
"reference": "order_abc123",
"paymentMethod": "ECOCASH",
"customerPhone": "263771234567",
"amount": 25.00,
"currency": "USD",
"description": "Payment for Order #123",
"callbackUrl": "https://mystore.com/webhook"
}Response
{
"success": true,
"message": "Charge initiated",
"data": {
"reference": "order_abc123",
"providerReference": "EC-78901234",
"paymentMethod": "ECOCASH",
"amount": 25.00,
"currency": "USD",
"status": "PROCESSING",
"message": "Payment initiated. Awaiting customer approval."
}
}Example
// sessionToken was minted server-side via POST /v1/payments/widget-session
// for this reference + amount + currency, then handed to the browser.
const response = await fetch('https://gateway.finivex.online/api/pg/v1/charges', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${sessionToken}`
},
body: JSON.stringify({
reference: 'order_abc123',
paymentMethod: 'ECOCASH',
customerPhone: '263771234567',
amount: 25.00,
currency: 'USD',
description: 'Payment for Order #123',
callbackUrl: 'https://mystore.com/webhook'
})
});
const { data } = await response.json();
// data.status is usually 'PROCESSING' — the customer approves on their phone.
// Poll GET /v1/charges/{reference} (or await your webhook) for the final status.
console.log(data.reference, data.status);# SESSION_TOKEN is minted server-side via POST /v1/payments/widget-session
curl -X POST https://gateway.finivex.online/api/pg/v1/charges \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $SESSION_TOKEN" \
-d '{
"reference": "order_abc123",
"paymentMethod": "ECOCASH",
"customerPhone": "263771234567",
"amount": 25.00,
"currency": "USD",
"description": "Payment for Order #123",
"callbackUrl": "https://mystore.com/webhook"
}'Look up the current status of a direct charge by the reference you supplied when creating it. Recommended poll interval: every 5 seconds until a terminal status is reached.
Response
{
"success": true,
"data": {
"reference": "order_abc123",
"providerReference": "EC-78901234",
"paymentMethod": "ECOCASH",
"amount": 25.00,
"currency": "USD",
"status": "COMPLETED",
"statusDescription": "Payment successful"
}
}Initiates a payment session. For mobile money this triggers a USSD push or returns a QR code. For cards this returns a redirect URL.
Request Body
{
"transactionId": "order_abc123",
"paymentMethod": "ECOCASH",
"amount": 25.00,
"currency": "USD",
"customerPhone": "263771234567",
"description": "Payment for Order #123",
"callbackUrl": "https://mystore.com/webhook"
}Response
{
"success": true,
"data": {
"transactionRef": "order_abc123",
"orderId": "EC-78901234",
"message": "Checkout initiated successfully",
"currency": "USD",
"amount": 25.00
}
}Poll this endpoint to check the current status of a payment. Recommended interval: every 5 seconds.
Query Parameters
| Name | Type | | Description |
|---|
| transactionId | string | required | Your unique transaction ID |
| paymentMethod | string | optional | Payment method code (e.g. ECOCASH) |
| externalRef | string | optional | Provider reference returned from checkout |
Response
{
"success": true,
"data": {
"transactionId": "order_abc123",
"status": "COMPLETED",
"amount": 25.00,
"paymentMethod": "ECOCASH",
"processedAt": "2026-03-24T10:30:05"
}
}Initiate a refund for a completed payment. Not all payment methods support refunds.
Query Parameters
| Name | Type | | Description |
|---|
| transactionId | string | required | Transaction ID to refund |
| reason | string | required | Reason for the refund |
| paymentMethod | string | required | Original payment method |
Returns the payment methods currently enabled for your merchant account.
{
"success": true,
"data": ["ECOCASH", "ONEMONEY", "VISA", "MASTERCARD"]
}Check if a specific payment provider is currently available.
{ "paymentMethod": "ECOCASH", "available": true }Webhooks
When a payment reaches a terminal status, we'll POST the transaction details to your callbackUrl.
Webhook Payload
{
"transactionId": "order_abc123",
"status": "COMPLETED",
"amount": 25.00,
"currency": "USD",
"paymentMethod": "ECOCASH",
"processedAt": "2026-03-24T10:30:05"
}Best Practices
✓Return a 2xx response promptly. Process asynchronously if needed.
✓Make your handler idempotent. Webhooks retry up to 5 times (every 60s).
✓Verify server-side by calling the status endpoint before fulfilling orders.
✓Use status polling as a fallback in case webhooks are delayed.
Payment Statuses
A payment will transition through these statuses during its lifecycle.
| Status | Description | Terminal? |
|---|
| PENDING | Payment initiated, awaiting customer action. | No |
| PROCESSING | Being processed by the payment provider. | No |
| COMPLETED | Payment was successful. | Yes |
| FAILED | Payment failed. | Yes |
| CANCELLED | Cancelled by the customer. | Yes |
| EXPIRED | Payment timed out. | Yes |
| REFUNDED | Payment was refunded. | Yes |
Error Handling
When a request fails, the response will have success: false with error details.
{
"success": false,
"errorCode": "ECOCASH_CHECKOUT_FAILED",
"errorMessage": "Insufficient balance",
"httpStatusCode": 400
}| Code | Description |
|---|
| CHECKOUT_FAILED | The checkout could not be initiated. |
| NETWORK_ERROR | Could not reach the payment provider. |
| INVALID_PHONE | Phone number format is invalid. |
| INSUFFICIENT_BALANCE | Customer has insufficient funds. |
| TIMEOUT | Payment was not completed in time. |
| REFUND_FAILED | Refund could not be processed. |