Merchant Documentation
This document describes merchant integration with the OnePay V1 REST API: payment acceptance, hosted checkout, refunds, payouts, saved cards, subscriptions, Apple Pay, Google Pay, and webhook notifications.
Quick Start
- Get merchant credentials from OnePay:
| Parameter | Where it is used |
|---|---|
tenant_id | Tenant identifier in OnePay. |
merchant_id | Merchant identifier. |
profile_id | Merchant profile: currency, terminals, routing rules, webhook URL. |
api_key_id | X-Merchant-Key-Id header. |
api_secret | X-Merchant-Key-Secret header; issued once during onboarding or rotation. |
webhook_signing_secret | Secret used to verify webhook signatures. |
Configure the webhook URL on the merchant profile via your manager or back office. The
webhook_urlis not passed in create requests: the gateway takes it from the profile and stores it on the operation.Create a payment:
curl -X POST "$API_BASE_URL/v1/payments" \
-H "Content-Type: application/json" \
-H "X-Merchant-Key-Id: $API_KEY_ID" \
-H "X-Merchant-Key-Secret: $API_SECRET" \
-H "Idempotency-Key: order-10001-create" \
-d '{
"tenant_id": "tenant-1",
"merchant_id": "merchant-1",
"profile_id": "profile-1",
"amount": 150000,
"currency": "KZT",
"payment_method": "card",
"merchant_order_id": "ORDER-10001",
"description": "Payment for order ORDER-10001",
"return_url": "https://merchant.example/orders/ORDER-10001/result"
}'Redirect the customer to
data.payment_page_url.Wait for a terminal-status webhook or check the payment with
GET /v1/payments/{payment_id}.
Base URL and Format
All requests use JSON:
Content-Type: application/jsonExamples below use:
API_BASE_URL=https://api.example.kzAmounts are sent in minor currency units. For KZT, 150000 means 1,500.00 KZT.
Authentication
For direct server-to-server integration, use API keys in headers:
| Header | Description |
|---|---|
X-Merchant-Key-Id | Merchant key identifier. |
X-Merchant-Key-Secret | Merchant key secret. |
Example:
X-Merchant-Key-Id: key_live_xxx
X-Merchant-Key-Secret: secret_live_xxxWith this scheme, tenant_id and merchant_id are passed in POST request bodies or GET query parameters.
Idempotency
For create operations, pass a unique key:
Idempotency-Key: order-10001-createRules:
| Scenario | Result |
|---|---|
| Same key and same payload | The previously created resource is returned. |
| Same key and different payload | 409 idempotency_mismatch. |
| New business order | Use a new key. |
The key can be passed in the Idempotency-Key header or in the JSON field idempotency_key. The header takes priority.
Standard Response Format
Successful responses use an envelope:
{
"success": true,
"error_code": 0,
"message": "OK",
"data": {
"payment_id": 12345,
"status": "created"
}
}Errors use the common format:
{
"success": false,
"error_code": 400,
"message": "amount is required",
"message_key": "validation_error",
"data": {
"field": "amount"
}
}Common HTTP statuses:
| HTTP | When it happens |
|---|---|
400 | Invalid JSON or validation error. |
401 | Invalid or missing merchant credentials. |
404 | Resource not found or not available to the merchant. |
409 | Status conflict, limits, balance, or idempotency conflict. |
502 | Bank connector is temporarily unavailable. |
500 | Internal gateway error. |
Payment Acceptance via Hosted Checkout
Flow
- The merchant creates a payment with
POST /v1/payments. - The gateway returns
payment_page_urlandclient_secret. - The customer opens
payment_page_urland enters card details on the gateway side. - If required, the customer completes 3DS.
- The gateway moves the payment to a terminal status and sends a webhook.
- The customer returns to
return_url.
Create Payment
POST /v1/payments
Required fields:
| Field | Type | Description |
|---|---|---|
tenant_id | string | Merchant tenant. |
merchant_id | string | Merchant. |
profile_id | string | Profile / terminal configuration. |
amount | int64 | Amount in minor units. |
currency | string | Currency, for example KZT. |
payment_method | string | Usually card; other methods may be enabled on the profile. |
return_url | string | Where to return the customer after payment. |
Optional fields:
| Field | Type | Description |
|---|---|---|
merchant_order_id | string | Order ID on the merchant side. |
description | string | Payment description. |
customer | object | Customer data. Required for saved-card / mandate flows. |
save_card | boolean | Create a mandate for future charges. |
mandate_id | string | Charge a previously saved card. |
line_items | array | Order cart. |
additional_charges_amount | int64 | Additional charges in minor units. |
discount_amount | int64 | Discount in minor units. |
Response example:
{
"success": true,
"error_code": 0,
"message": "OK",
"data": {
"payment_id": 12345,
"tenant_id": "tenant-1",
"merchant_id": "merchant-1",
"profile_id": "profile-1",
"status": "created",
"amount": 150000,
"currency": "KZT",
"payment_method": "card",
"merchant_order_id": "ORDER-10001",
"client_secret": "sec_xxx",
"payment_page_url": "https://pay.example.kz/checkout/sec_xxx",
"redirect_url": "",
"created_at": "2026-08-17T10:00:00Z",
"updated_at": "2026-08-17T10:00:00Z"
}
}Check Payment
GET /v1/payments/{payment_id}?tenant_id=...&merchant_id=...
curl "$API_BASE_URL/v1/payments/12345?tenant_id=tenant-1&merchant_id=merchant-1" \
-H "X-Merchant-Key-Id: $API_KEY_ID" \
-H "X-Merchant-Key-Secret: $API_SECRET"Payment Statuses
| Status | Meaning |
|---|---|
created | Payment was created; the customer has not completed payment yet. |
processing | The gateway is processing the payment. |
waiting_for_auth | External authentication is required, for example 3DS. |
pending | Waiting for the final bank response. |
succeeded | Payment succeeded. |
failed | Payment was declined or failed. |
refunded | Payment was fully refunded. |
Terminal payment statuses: succeeded, failed, refunded.
Server-to-Server Payment Execution
If the merchant has permission and a PCI-compliant environment, card data can be sent directly to POST /v1/payments/{payment_id}/execute.
curl -X POST "$API_BASE_URL/v1/payments/12345/execute" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "tenant-1",
"merchant_id": "merchant-1",
"client_secret": "sec_xxx",
"card": {
"pan": "4111111111111111",
"month": "12",
"year": "2030",
"holder": "IVAN IVANOV",
"cvv": "123"
},
"browser_info": {
"ip_address": "203.0.113.10",
"user_agent": "Mozilla/5.0",
"accept_header": "text/html,application/xhtml+xml",
"accept_language": "en-US,en;q=0.9",
"language": "en",
"screen_width": 1440,
"screen_height": 900,
"color_depth": 24,
"time_zone_offset": -300,
"java_enabled": false,
"js_enabled": true
}
}'If redirect_url is returned, redirect the customer to that URL.
3DS pages and bank return are handled by gateway endpoints: /v1/payments/{payment_id}/3ds/start and /v1/payments/{payment_id}/3ds/return. Usually the merchant does not call them directly.
Cancel Payment Before Final Status
POST /v1/payments/{payment_id}/cancel
Cancellation is available for payments in created or waiting_for_auth. It is a local gateway cancellation without a bank call.
curl -X POST "$API_BASE_URL/v1/payments/12345/cancel" \
-H "Content-Type: application/json" \
-H "X-Merchant-Key-Id: $API_KEY_ID" \
-H "X-Merchant-Key-Secret: $API_SECRET" \
-H "Idempotency-Key: order-10001-cancel" \
-d '{
"tenant_id": "tenant-1",
"merchant_id": "merchant-1",
"profile_id": "profile-1",
"reason": "buyer abandoned checkout"
}'Refunds
POST /v1/refunds
A refund can be created only for a successful payment. If amount is omitted, the gateway attempts to refund the full available balance. For a partial refund, pass amount in minor units.
curl -X POST "$API_BASE_URL/v1/refunds" \
-H "Content-Type: application/json" \
-H "X-Merchant-Key-Id: $API_KEY_ID" \
-H "X-Merchant-Key-Secret: $API_SECRET" \
-H "Idempotency-Key: refund-10001-1" \
-d '{
"tenant_id": "tenant-1",
"merchant_id": "merchant-1",
"profile_id": "profile-1",
"payment_id": 12345,
"amount": 50000,
"reason": "partial return"
}'Refund statuses: created, processing, pending, succeeded, failed. Terminal statuses: succeeded, failed.
Customer Payouts
Customer payout is a payout to an end recipient from the merchant balance. It is not the same as settlement to the merchant.
Create Payout
POST /v1/payouts
curl -X POST "$API_BASE_URL/v1/payouts" \
-H "Content-Type: application/json" \
-H "X-Merchant-Key-Id: $API_KEY_ID" \
-H "X-Merchant-Key-Secret: $API_SECRET" \
-H "Idempotency-Key: payout-20001" \
-d '{
"tenant_id": "tenant-1",
"merchant_id": "merchant-1",
"profile_id": "profile-1",
"amount": 250000,
"currency": "KZT",
"destination_type": "card",
"destination_ref": "card_token_abc",
"recipient_name": "Ivan",
"recipient_surname": "Ivanov",
"description": "Customer refund to card"
}'Fields:
| Field | Type | Description |
|---|---|---|
amount | int64 | Payout amount in minor units. |
currency | string | Payout currency. |
destination_type | string | card or iban. |
destination_ref | string | Card token/reference or IBAN. For card, raw PAN must not be sent. |
recipient_name | string | Recipient first name, if required by the connector. |
recipient_surname | string | Recipient last name, if required by the connector. |
payout_cinfo | string | Additional information for the payout connector. |
description | string | Payout description. |
If the available balance is insufficient, the API returns 409.
Get Payout
GET /v1/payouts/{payout_id}?tenant_id=...&merchant_id=...
Payout statuses: created, processing, pending, succeeded, failed.
Batch Payouts
POST /v1/payout-batches
curl -X POST "$API_BASE_URL/v1/payout-batches" \
-H "Content-Type: application/json" \
-H "X-Merchant-Key-Id: $API_KEY_ID" \
-H "X-Merchant-Key-Secret: $API_SECRET" \
-H "Idempotency-Key: batch-2026-08-17-1" \
-d '{
"tenant_id": "tenant-1",
"merchant_id": "merchant-1",
"profile_id": "profile-1",
"items": [
{
"item_id": "item-1",
"item_idempotency_key": "batch-2026-08-17-1-item-1",
"amount": 100000,
"currency": "KZT",
"destination_type": "card",
"destination_ref": "card_token_1"
},
{
"item_id": "item-2",
"item_idempotency_key": "batch-2026-08-17-1-item-2",
"amount": 150000,
"currency": "KZT",
"destination_type": "iban",
"destination_ref": "KZ000000000000000000"
}
]
}'Batch statuses: created, processing, succeeded, failed, partially_succeeded.
Get a batch with: GET /v1/payout-batches/{batch_id}?tenant_id=...&merchant_id=....
Saved Cards and Recurring Payments
Save a Card for Future Charges
Create a regular payment with save_card: true and a filled customer. The customer completes hosted checkout. After successful payment, responses and webhooks may include mandate_id.
{
"tenant_id": "tenant-1",
"merchant_id": "merchant-1",
"profile_id": "profile-1",
"amount": 10000,
"currency": "KZT",
"payment_method": "card",
"return_url": "https://merchant.example/cards/result",
"save_card": true,
"customer": {
"customer_id": "customer-100",
"name": "Ivan Ivanov",
"email": "ivan@example.kz",
"phone": "+77010000000"
}
}List Saved Cards
GET /v1/mandates?tenant_id=...&merchant_id=...&customer_id=...
Cancel Saved Card
POST /v1/mandates/{mandate_id}/cancel?tenant_id=...&merchant_id=...
Pay with a Saved Card
Create a payment with payment_method: "mandate" and mandate_id.
{
"tenant_id": "tenant-1",
"merchant_id": "merchant-1",
"profile_id": "profile-1",
"amount": 99000,
"currency": "KZT",
"payment_method": "mandate",
"mandate_id": "mandate_xxx",
"merchant_order_id": "ORDER-RECUR-1",
"return_url": "https://merchant.example/orders/ORDER-RECUR-1/result"
}Subscriptions
A subscription creates regular charges using a mandate.
POST /v1/subscriptions
curl -X POST "$API_BASE_URL/v1/subscriptions" \
-H "Content-Type: application/json" \
-H "X-Merchant-Key-Id: $API_KEY_ID" \
-H "X-Merchant-Key-Secret: $API_SECRET" \
-H "Idempotency-Key: sub-customer-100-basic" \
-d '{
"tenant_id": "tenant-1",
"merchant_id": "merchant-1",
"profile_id": "profile-1",
"mandate_id": "mandate_xxx",
"amount": 299000,
"currency": "KZT",
"schedule": "monthly",
"customer_id": "customer-100",
"customer_email": "ivan@example.kz",
"customer_name": "Ivan Ivanov"
}'Main endpoints:
| Method | Path | Purpose |
|---|---|---|
GET | /v1/subscriptions | List subscriptions. |
GET | /v1/subscriptions/{subscription_id} | Get a subscription. |
POST | /v1/subscriptions/{subscription_id}/pause | Pause. |
POST | /v1/subscriptions/{subscription_id}/resume | Resume. |
POST | /v1/subscriptions/{subscription_id}/cancel | Cancel. |
POST | /v1/subscriptions/{subscription_id}/cancel-options | Cancel with options, for example cancel_at_period_end. |
POST | /v1/subscriptions/{subscription_id}/change-plan | Change plan. |
POST | /v1/subscriptions/{subscription_id}/charge | Manual charge. |
GET | /v1/subscriptions/{subscription_id}/charges | List charges. |
POST | /v1/subscriptions/{subscription_id}/charges/{charge_id}/refund | Refund a charge. |
Available Payment Methods
GET /v1/payment-methods?tenant_id=...&merchant_id=...&profile_id=...
The response shows methods enabled by current routing rules and connector settings.
Call this endpoint before showing payment methods if different profiles or terminals support different methods. For example, one profile may accept only card, while another may accept card, applepay, and googlepay.
{
"success": true,
"error_code": 0,
"message": "OK",
"data": {
"payment_methods": [
{ "type": "card", "enabled": true }
]
}
}Checkout Endpoints for Frontend
Hosted checkout uses browser-facing endpoints without the merchant API secret:
| Method | Path | Purpose |
|---|---|---|
GET | /v1/checkout/{client_secret} | HTML checkout page or JSON session when Accept: application/json / ?format=json is used. |
POST | /v1/checkout/{client_secret}/cancel | Cancel a checkout session by client_secret, for example when the customer goes back to the store. |
The client_secret belongs only to a specific payment intent. Do not expose merchant credentials in the browser.
If you use the ready payment_page_url, you usually do not need to call these endpoints directly. JSON mode GET /v1/checkout/{client_secret}?format=json is useful for custom payment-page integrations where the merchant frontend shows checkout-session state but does not receive the merchant API secret.
Apple Pay and Google Pay
Apple Pay and Google Pay are payment methods inside the regular pay-in flow. First check that the method is available through /v1/payment-methods, then create a payment with payment_method: "applepay" or payment_method: "googlepay".
| Method | Path | Purpose |
|---|---|---|
POST | /v1/apple-pay/validate-merchant | Apple Pay merchant session validation; requires client_secret of a payment with payment_method: "applepay". |
Apple Pay and Google Pay must be enabled on the profile / routing configuration. If the method is not configured, the endpoint may return 404.
Google Pay
Google Pay ™ is a convenient and secure way to pay for purchases with cards saved in a Google account. Customers can use Google Pay on websites, in apps, and in other places where this payment method is available.
Before integrating with OnePay, review the Google documentation:
- Integration types and official Google documentation
- Google Pay documentation for websites
- Google Pay integration checklist for websites
- Google Pay brand guidelines for websites
- Google Pay & Wallet Console
- Google Pay API Acceptable Use Policy
- Google Pay API Terms of Service
Google Pay ™ Benefits
- Convenience: customers choose a saved card and do not manually enter payment details for every purchase.
- Speed: payment is faster because the required payment details are already available in Google Pay.
- Security: Google Pay uses tokenization, encryption, and additional authentication mechanisms to protect payment data.
- Broad support: Google Pay can be used on websites and in apps where this payment method is enabled.
How Google Pay ™ Works
NFC Technology
Google Pay supports contactless payments using NFC on compatible devices. The customer can confirm the payment on the device and pay without sharing card details with the seller.
Online Purchases
Google Pay is also used for online payments. At checkout on a website, the customer selects Google Pay as the payment method and completes the purchase without manually entering card details.
Compatibility
Google Pay is available on supported devices and in supported browsers. Before launching payments on a website, follow Google's requirements for web integration, testing, and payment-button branding.
What Is Required to Connect
To connect Google Pay on the payment page, notify your OnePay manager. The manager will advise on requirements and next steps. Google Pay is enabled on the acquiring and payment-page side. Google Pay can be used to accept payments through the payment page.
SCA and PSD2
Google Pay supports tokenized payments, which are usually the preferred option. In most cases, issuers accept this scenario as meeting strong customer authentication requirements.
All merchants must comply with the Google Pay API Acceptable Use Policy and the Google Pay API Terms of Service. Google Pay is a trademark of Google LLC.
Apple Pay
Apple Pay is Apple’s online payment system for payments on websites and in apps. Web integration uses Apple Pay on the Web.
Before integrating, review the Apple documentation:
- Apple Pay on the Web
- Providing Merchant Validation
- Apple Pay on the Web troubleshooting guide
- Human Interface Guidelines: Apple Pay
For Apple Pay, the domain where the payment button is shown must be registered and verified. Merchant validation is performed through POST /v1/apple-pay/validate-merchant.
The Apple Pay payment token must be passed in apple_pay_token.
Webhook Notifications
The webhook URL is configured on the merchant profile. When a payment, payout, or subscription is created, the gateway stores the current URL on the operation. Changing the profile URL later does not change delivery address for already created operations.
Stable Format
Headers:
| Header | Description |
|---|---|
X-Webhook-Id | Event ID. |
X-Webhook-Timestamp | Unix timestamp in seconds. |
X-Webhook-Attempt | Delivery attempt number. |
X-Signature | Hex HMAC-SHA256 signature. |
Signature:
hex(HMAC_SHA256(webhook_signing_secret, "<event_id>.<timestamp>.<raw_body>"))Node.js verification example:
import crypto from "node:crypto";
export function verifyOnePayWebhook({ secret, eventId, timestamp, rawBody, signature }) {
const payload = `${eventId}.${timestamp}.${rawBody}`;
const expected = crypto.createHmac("sha256", secret).update(payload).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}Payment webhook body example:
{
"event_id": "evt_123",
"event_type": "payment.succeeded",
"payment_id": 12345,
"tenant_id": "tenant-1",
"merchant_id": "merchant-1",
"profile_id": "profile-1",
"status": "succeeded",
"amount": 150000,
"currency": "KZT",
"merchant_order_id": "ORDER-10001",
"rrn": "123456789012",
"external_reference": "bank-operation-123",
"created_at": "2026-08-17T10:00:00Z",
"updated_at": "2026-08-17T10:02:00Z"
}Common events:
| Event | When it is sent |
|---|---|
payment.succeeded | Payment completed successfully. |
payment.failed | Payment failed. |
payment.refunded | Payment was fully refunded. |
refund.created | Refund was created. |
refund.succeeded | Refund succeeded. |
refund.failed | Refund failed. |
payout.created | Payout was created. |
payout.succeeded | Payout succeeded. |
payout.failed | Payout failed. |
payout_batch.succeeded | All batch payout items succeeded. |
payout_batch.partially_succeeded | The batch has both successful and failed items. |
subscription.* | Subscription lifecycle events. |
The merchant must respond to webhooks with HTTP 2xx. If delivery fails, the gateway retries according to the retry policy. A webhook retry does not mean that the payment operation was repeated; the merchant handler must be idempotent by X-Webhook-Id.
Test Integration
Recommended checklist:
| Scenario | What to check |
|---|---|
| Successful pay-in | Payment creation, checkout redirect, payment.succeeded, webhook. |
| Failed pay-in | Terminal failed, correct error_code. |
| 3DS | waiting_for_auth, redirect URL, return from 3DS, final webhook. |
| Repeated create | The same Idempotency-Key does not create a second payment. |
| Refund | Partial and full refund, refund.succeeded, payment.refunded. |
| Payout | Success, failure, insufficient balance. |
| Webhook retry | Re-delivery does not duplicate the order on the merchant side. |
Differences from Some PSP Documentation
In some payment systems, callback URL and request-signature parameters are passed in each create request. In OnePay V1:
- merchant API uses
X-Merchant-Key-IdandX-Merchant-Key-Secretheaders, not an HMAC signature on each request; - webhook URL is managed on the profile, not in the operation body;
- amounts are passed in minor units;
Idempotency-Keyis required for safe create/refund/payout retries;- hosted checkout is the recommended way to collect card data.