Skip to content

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

  1. Get merchant credentials from OnePay:
ParameterWhere it is used
tenant_idTenant identifier in OnePay.
merchant_idMerchant identifier.
profile_idMerchant profile: currency, terminals, routing rules, webhook URL.
api_key_idX-Merchant-Key-Id header.
api_secretX-Merchant-Key-Secret header; issued once during onboarding or rotation.
webhook_signing_secretSecret used to verify webhook signatures.
  1. Configure the webhook URL on the merchant profile via your manager or back office. The webhook_url is not passed in create requests: the gateway takes it from the profile and stores it on the operation.

  2. Create a payment:

bash
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"
  }'
  1. Redirect the customer to data.payment_page_url.

  2. Wait for a terminal-status webhook or check the payment with GET /v1/payments/{payment_id}.

Base URL and Format

All requests use JSON:

http
Content-Type: application/json

Examples below use:

bash
API_BASE_URL=https://api.example.kz

Amounts 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:

HeaderDescription
X-Merchant-Key-IdMerchant key identifier.
X-Merchant-Key-SecretMerchant key secret.

Example:

http
X-Merchant-Key-Id: key_live_xxx
X-Merchant-Key-Secret: secret_live_xxx

With 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:

http
Idempotency-Key: order-10001-create

Rules:

ScenarioResult
Same key and same payloadThe previously created resource is returned.
Same key and different payload409 idempotency_mismatch.
New business orderUse 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:

json
{
  "success": true,
  "error_code": 0,
  "message": "OK",
  "data": {
    "payment_id": 12345,
    "status": "created"
  }
}

Errors use the common format:

json
{
  "success": false,
  "error_code": 400,
  "message": "amount is required",
  "message_key": "validation_error",
  "data": {
    "field": "amount"
  }
}

Common HTTP statuses:

HTTPWhen it happens
400Invalid JSON or validation error.
401Invalid or missing merchant credentials.
404Resource not found or not available to the merchant.
409Status conflict, limits, balance, or idempotency conflict.
502Bank connector is temporarily unavailable.
500Internal gateway error.

Payment Acceptance via Hosted Checkout

Flow

  1. The merchant creates a payment with POST /v1/payments.
  2. The gateway returns payment_page_url and client_secret.
  3. The customer opens payment_page_url and enters card details on the gateway side.
  4. If required, the customer completes 3DS.
  5. The gateway moves the payment to a terminal status and sends a webhook.
  6. The customer returns to return_url.

Create Payment

POST /v1/payments

Required fields:

FieldTypeDescription
tenant_idstringMerchant tenant.
merchant_idstringMerchant.
profile_idstringProfile / terminal configuration.
amountint64Amount in minor units.
currencystringCurrency, for example KZT.
payment_methodstringUsually card; other methods may be enabled on the profile.
return_urlstringWhere to return the customer after payment.

Optional fields:

FieldTypeDescription
merchant_order_idstringOrder ID on the merchant side.
descriptionstringPayment description.
customerobjectCustomer data. Required for saved-card / mandate flows.
save_cardbooleanCreate a mandate for future charges.
mandate_idstringCharge a previously saved card.
line_itemsarrayOrder cart.
additional_charges_amountint64Additional charges in minor units.
discount_amountint64Discount in minor units.

Response example:

json
{
  "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=...

bash
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

StatusMeaning
createdPayment was created; the customer has not completed payment yet.
processingThe gateway is processing the payment.
waiting_for_authExternal authentication is required, for example 3DS.
pendingWaiting for the final bank response.
succeededPayment succeeded.
failedPayment was declined or failed.
refundedPayment 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.

bash
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.

bash
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.

bash
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

bash
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:

FieldTypeDescription
amountint64Payout amount in minor units.
currencystringPayout currency.
destination_typestringcard or iban.
destination_refstringCard token/reference or IBAN. For card, raw PAN must not be sent.
recipient_namestringRecipient first name, if required by the connector.
recipient_surnamestringRecipient last name, if required by the connector.
payout_cinfostringAdditional information for the payout connector.
descriptionstringPayout 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

bash
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.

json
{
  "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.

json
{
  "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

bash
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:

MethodPathPurpose
GET/v1/subscriptionsList subscriptions.
GET/v1/subscriptions/{subscription_id}Get a subscription.
POST/v1/subscriptions/{subscription_id}/pausePause.
POST/v1/subscriptions/{subscription_id}/resumeResume.
POST/v1/subscriptions/{subscription_id}/cancelCancel.
POST/v1/subscriptions/{subscription_id}/cancel-optionsCancel with options, for example cancel_at_period_end.
POST/v1/subscriptions/{subscription_id}/change-planChange plan.
POST/v1/subscriptions/{subscription_id}/chargeManual charge.
GET/v1/subscriptions/{subscription_id}/chargesList charges.
POST/v1/subscriptions/{subscription_id}/charges/{charge_id}/refundRefund 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.

json
{
  "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:

MethodPathPurpose
GET/v1/checkout/{client_secret}HTML checkout page or JSON session when Accept: application/json / ?format=json is used.
POST/v1/checkout/{client_secret}/cancelCancel 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".

MethodPathPurpose
POST/v1/apple-pay/validate-merchantApple 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:

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:

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:

HeaderDescription
X-Webhook-IdEvent ID.
X-Webhook-TimestampUnix timestamp in seconds.
X-Webhook-AttemptDelivery attempt number.
X-SignatureHex HMAC-SHA256 signature.

Signature:

text
hex(HMAC_SHA256(webhook_signing_secret, "<event_id>.<timestamp>.<raw_body>"))

Node.js verification example:

js
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:

json
{
  "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:

EventWhen it is sent
payment.succeededPayment completed successfully.
payment.failedPayment failed.
payment.refundedPayment was fully refunded.
refund.createdRefund was created.
refund.succeededRefund succeeded.
refund.failedRefund failed.
payout.createdPayout was created.
payout.succeededPayout succeeded.
payout.failedPayout failed.
payout_batch.succeededAll batch payout items succeeded.
payout_batch.partially_succeededThe 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:

ScenarioWhat to check
Successful pay-inPayment creation, checkout redirect, payment.succeeded, webhook.
Failed pay-inTerminal failed, correct error_code.
3DSwaiting_for_auth, redirect URL, return from 3DS, final webhook.
Repeated createThe same Idempotency-Key does not create a second payment.
RefundPartial and full refund, refund.succeeded, payment.refunded.
PayoutSuccess, failure, insufficient balance.
Webhook retryRe-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-Id and X-Merchant-Key-Secret headers, 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-Key is required for safe create/refund/payout retries;
  • hosted checkout is the recommended way to collect card data.