Skip to main content

Native Payments

Use Native Payments when you want full control of the client experience (web or mobile) and prefer not to use the Checkout SDK. Your client or backend collects a payment payload and sends it to Ottu to process the payment for a given session_id.

A payment payload can be:

  • Wallet payment data (e.g., Apple Pay paymentData, Google Pay paymentMethodData) — typically encrypted by the wallet.
  • Gateway token / network token (card-on-file or one-click use cases) — not necessarily encrypted.

Ottu processes the payload with the configured gateway and returns a normalized callback result.

Boost Your Integration

Ottu offers SDKs and tools to speed up your integration. See Getting Started for all available options.

When to Use​

  • Apple Pay or Google Pay buttons are rendered and managed by you.
  • Existing tokenization has already been implemented and needs to be used to charge with a gateway token.
  • Granular, SDK-less control of the UX is required, while Ottu's orchestration and gateway integrations are still leveraged.

Setup​

  • A valid session_id obtained from the Checkout API.
  • A Merchant Gateway ID (MID) with the payment service activated and properly configured in Ottu.

If multiple gateways are configured, always include the pg_code corresponding to the MID that has the target payment service enabled.
Example:
If a transaction has knet and mpgs pg_code but only knet supports Apple Pay, you must send
pg_code: knet when calling the Apple Pay endpoint.

Checklist​

  • Created a valid session_id.
  • Completed Apple Pay / Google Pay setup (if applicable).
  • Selected the correct invocation model (client or backend).
  • Used the appropriate API key type (Public API Key vs. Private API Key).
  • Implemented backend sync logic.

Configuring your native wallet from the session response​

Before you can render an Apple Pay or Google Pay button, you have to build the wallet's payment request. Several of those values — the gateway identifier, the gateway merchant ID, your wallet merchant ID — must come from Ottu, not from your own configuration. Ottu already returns them per transaction in the session response, so you never have to hardcode them.

gateway_merchant_id is not your wallet merchant_id

These are two different values and they are not interchangeable:

  • gateway_merchant_id — the payment gateway's merchant identifier (for example, your MPGS merchant ID). This is what goes into the wallet's gateway tokenization setting (gatewayMerchantId for Google Pay).
  • merchant_id — your wallet (Google / Apple) merchant identifier (for example, a Google Pay BCR2DN… ID). It is used only for the wallet's own merchant info.

Putting the wallet merchant_id where the gateway expects gateway_merchant_id is a common, hard-to-diagnose mistake: the wallet sheet renders and produces a token, but the gateway rejects that token at the payment step (MPGS, for example, returns "not authorized to use this Google Pay payment token"). Always read both values from the session response per transaction — never hardcode them.

Where the fields live​

The wallet configuration is returned under sdk_setup_preload_payload.payment_services[] — one object per configured wallet. Request it by setting include_sdk_setup_preload=true when you create or retrieve the session.

These values are not in the top-level payment_methods array (which can be empty for native flows). Read each wallet's config from payment_services[], matched by its flow discriminator ("google_pay" or "apple_pay") and its pg_code.

Google Pay​

Map each payment_services[] field to its Google Pay request setting:

payment_services[] fieldGoogle Pay settingNotes
gatewaytokenization gatewaye.g. mpgs
gateway_merchant_idtokenization gatewayMerchantIdthe gateway's merchant ID — not your Google merchant ID
merchant_idmerchantInfo.merchantIdyour Google Pay (BCR2DN…) merchant ID
merchant_namemerchantInfo.merchantNamedisplay name
environmentGoogle Pay environmentPRODUCTION / TEST
currency_codetransaction currency
country_codetransaction country
total_pricetransaction total

After Google Pay returns the token, POST it to the native endpoint (payment_url, i.e. POST /pbl/v2/payment/google-pay/) with session_id, pg_code, and the payload — as shown in Step-by-Step below.

Apple Pay​

Apple Pay's payment_services[] entry has a different field set — verified against the live SDK response, it does not mirror Google Pay:

payment_services[] fieldApple Pay payment request useNotes
merchant_idApple Pay merchant identifierthe merchantIdentifier used when validating the merchant session
domainmerchant domainthe registered domain used for Apple Pay merchant/session validation
shop_namedisplay nameshown on the Apple Pay sheet
currency_codepayment request currency
country_codepayment request country
amountpayment request totalApple Pay uses amount here, not total_price
session_idOttu sessionused for the merchant-session validation step
validation_urlOttu validation endpointyour client calls this to validate the Apple Pay merchant session before showing the sheet
Apple Pay does not expose gateway tokenization fields

Unlike Google Pay, the Apple Pay entry does not contain gateway or gateway_merchant_id, and there is no environment field:

  • The gateway tokenization (the equivalent of Google Pay's gatewayMerchantId) is handled by Ottu server-side during the merchant-session validation (validation_url) and decryption — you do not set it in the client payment request.
  • The Apple Pay environment (sandbox vs. production) is determined by the Apple account and device, not by a value in the response.

After Apple Pay returns the encrypted paymentData, POST it to the native endpoint (payment_url, i.e. POST /pbl/v2/payment/apple-pay/) with session_id, pg_code, and the payload — as shown in Step-by-Step below.

Guide​

Workflow​

Client → Ottu​

  1. The client collects the wallet or tokenized payment payload and calls the Native Payments endpoint directly.
  2. The client receives the API callback response.
Never expose private keys on the client side

Never embed Private API Keys in client-side code — they grant full API access and will be compromised if exposed. Use a Public API Key for client-side calls.

If the call is made from the client side, the backend must be synchronized with the payment result by ensuring that one of the following actions is performed:

  • The API response is forwarded to the backend, or
  • The Payment Status Query API is called by the backend after the client confirms that the payment has been completed.
  1. The client sends the payment payload to the backend.
  2. The backend calls the Ottu Native Payments endpoint.
  3. The backend receives the payment response callback.
  4. The backend processes the callback response and notifies the client side with the payment status.

Step-by-Step​

Apple Pay native payment
curl -X POST "https://sandbox.ottu.net/b/pbl/v2/payment/apple-pay/" \
-H "Authorization: Api-Key your_api_key" \
-H "Content-Type: application/json" \
-d '{
"session_id": "your_session_id",
"pg_code": "apple-pay-gateway",
"payload": {
"paymentData": {
"data": "base64_encrypted_payment_data...",
"signature": "base64_signature...",
"header": {
"publicKeyHash": "hash...",
"ephemeralPublicKey": "key..."
},
"version": "EC_v1"
},
"paymentMethod": {
"displayName": "Visa 5766",
"network": "Visa",
"type": "debit"
},
"transactionIdentifier": "transaction_id..."
}
}'

Response (all endpoints return the same structure):

{
"result": "success",
"message": "successful payment",
"pg_response": {}
}

Use the response values to reconcile the payment in your backend and update your order state.

Idempotency​

Native Payments are direct-charge endpoints: one request charges the customer immediately, so a flaky network or an over-eager retry can charge twice. To make a charge safe to retry, send an Idempotency-Key request header. Generate one value per charge attempt — a UUID works well — and send that same value on every retry of that attempt; a fresh value for the same charge would defeat the protection:

Auto-debit charge with Idempotency-Key
curl -X POST "https://sandbox.ottu.net/b/pbl/v2/payment/auto-debit/" \
-H "Authorization: Api-Key your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5f3b9c2a-1e4d-4a7b-9c8e-2d6f0a1b3c4d" \
-d '{ "session_id": "your_session_id", "token": "saved_card_token" }'

The same Idempotency-Key header works on every direct-charge endpoint — Apple Pay, Google Pay, wallet, and cash included.

The key is scoped to the transaction (its session_id) and recorded only after a successful charge. That single rule produces three behaviors:

ScenarioWhat happens
Replaying a key from a successful chargeRejected with 409 Conflict before any charge — the customer is never double-charged
Reusing a key after a failed chargeThe charge proceeds — failed payments stay retryable, because the key was never recorded
Sending no Idempotency-Key headerNo replay protection — unchanged behavior, existing integrations unaffected

A blocked replay returns 409 Conflict:

409 Conflict — replayed Idempotency-Key
{
"detail": "Duplicate request detected. Idempotency-Key already used.",
"result": "failed"
}

The contract applies to every direct-charge endpoint:

EndpointCharge
POST /b/pbl/v2/payment/apple-pay/Apple Pay
POST /b/pbl/v2/payment/google-pay/Google Pay
POST /b/pbl/v2/payment/auto-debit/Saved-token / recurring
POST /b/pbl/v2/payment/wallet/M-Wallet balance
POST /b/pbl/v2/payment/cash/Cash acknowledgement
Idempotency-Key vs. Tracking-Key

This is distinct from the Tracking-Key header used by the Operations API (refund, capture, void). A replayed Tracking-Key returns the latest status of the original operation, whereas a replayed Idempotency-Key on a direct charge is rejected with 409. Use Idempotency-Key for charges, Tracking-Key for operations.

Concurrent duplicates

Two identical requests sent at the same instant are serialized internally — the first to claim the charge proceeds, the other gets 409 Conflict. Prefer sequential retries (wait for a response or timeout before retrying) over firing duplicates in parallel.

Use Cases​

The general Setup prerequisites and checklist apply to all providers below.

danger

Never modify wallet payloads (Apple Pay, Google Pay) — any change invalidates token decryption. Always include pg_code if multiple gateways are configured.

  1. Configure Apple Pay on the client side (iOS / web).
  2. Collect the encrypted paymentData object from Apple Pay.
  3. Send the payload with the session_id to POST /b/pbl/v2/payment/apple-pay/.
  4. Ottu processes via the configured Apple Pay gateway and returns a unified result (succeeded, failed).

API Reference​

Select the payment provider to see its full interactive API schema:

Native Payment API(Apple Pay)​

Native Payment API(Apple Pay)

POST 

/b/pbl/v2/payment/apple-pay/

Allows merchants to submit an Apple Pay payment directly via server-to-server integration. This endpoint requires private key authentication and expects a valid Apple Pay token structure in the request payload under payload.

Typical use case: The merchant collects the Apple Pay token on their frontend and sends it to this endpoint along with session and amount information.

Permissions​

Auth MethodRequired Permissions
API KeyAll permissions (admin access)
Basic AuthCan add payment requests or Can add e-commerce payments

Idempotency. Pass an Idempotency-Key request header to make this charge safe to retry. If the same key was already recorded on the transaction by a previous successful charge, the request is rejected with 409 Conflict before any charge is attempted — so a network retry can never double-charge the customer. The key is stored only after a successful charge, so retrying a failed charge with the same key is still allowed. Omitting the header preserves the original, non-idempotent behavior. See Native Payments → Idempotency for the full contract.

curl --location 'https://sandbox.ottu.net/b/pbl/v2/payment/apple-pay/' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Api-Key YOUR_API_KEY' \
--data '{
  "payload": {
    "paymentData": {
      "version": "string",
      "data": "string",
      "signature": "string",
      "header": {
        "ephemeralPublicKey": "string",
        "wrappedKey": "string",
        "publicKeyHash": "string",
        "transactionId": "string"
      }
    },
    "paymentMethod": {
      "displayName": "string",
      "network": "string",
      "type": "string"
    },
    "transactionIdentifier": "string"
  },
  "pg_code": "string",
  "session_id": "string"
}'

Request​

Responses​

FAQ​

What's Next?​

  • Checkout API — Create sessions with payment_instrument for one-step checkout
  • Recurring Payments — Use tokens for auto-debit payments
  • Webhooks — Receive payment result notifications