/api/payments/hostedIdempotency-Key requiredCreate a payment
Returns 201 with the new payment. Resending the same request with the same Idempotency-Key returns 200 with the same payment.
Request body
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
merchant_order_id | string | Required | Your order reference: 1–64 characters from A-Z a-z 0-9 . _ -. Each reference can be used for one payment per environment, even if that payment failed or expired. Uniqueness is case-insensitive: ORDER-1 and order-1 are treated as the same reference. | ORDER-2026-001 |
amount | string | Required | Positive amount with two decimal places and no leading zeros, for example "100.00". Must be within your account limits. | "100.00" |
currency | string | Optional | If omitted, the system main currency applies.USDEURAEDTRY | USD |
webhook_url | string · uri | Required | Endpoint that receives the webhooks for this payment. Public HTTPS on port 443, at most 500 characters, without user info or fragment. Private and local addresses are not accepted, including in sandbox, and the host name must resolve to a public IP address at the time of the request; otherwise 422 9000008. | https://merchant.example/webhooks/payments |
success_url | string · uri | Required | Redirect target for the browser after a successful payment. HTTPS, at most 2048 characters, without user info or fragment. Sandbox also accepts http://localhost, 127.0.0.1 and [::1]. | https://merchant.example/payment/success |
fail_url | string · uri | Required | Redirect target for the browser when the payment fails, expires or is cancelled. The same rules as for success_url apply. | https://merchant.example/payment/failure |
customer | object | Required | The payer's contact details, used for the payment result, the receipt and the bank's verification steps. Only email and phone are accepted; any other field inside customer is rejected with 422 1000001. | { … } |
customer.email | string | Required | The payer's real e-mail address: RFC 5322, at most 254 characters, with a dotted domain. It is stored in lower case. | [email protected] |
customer.phone | string | Required | The payer's real phone number in international format (+ country code, E.164), for example +37061234567. Türkiye-specific rule: +90 followed by the 10-digit national number (5xx mobile, 2xx, 3xx, 4xx landline, 8xx special). A number sent without a country code is treated as a Turkish number (+90). | +37061234567 |
These three URLs apply to this payment only and are not taken from your panel settings.
The payment page does not ask the payer for the e-mail address or the phone number again. For privacy, customer is not returned in the payment object or in webhooks.
| Field | Reason | Message in errors |
|---|---|---|
customer.email | Invalid format or longer than 254 characters. | customer.email must be a valid e-mail address (max 254 characters). |
customer.email | Placeholder, example or disposable address. | customer.email must be the payer's real address; placeholder, example or disposable addresses (example.com, test@…, mailinator…) are not accepted. |
customer.phone | Not a valid number in international format. | customer.phone must be a valid phone number in international format (e.g. +37061234567). |
customer.phone | Repeated or sequential digit pattern. | customer.phone must be the payer's real number; repeated or sequential digit patterns (5555555555, 1234567890) are not accepted. |
customer.phone | Turkish number not in the form +90 followed by 10 digits starting with 2, 3, 4, 5 or 8. | customer.phone for Türkiye must be +90 followed by a 10-digit number starting with 2, 3, 4, 5 or 8 (e.g. +905321234567). |
HTTP 422 · 1000001
{
"type": "https://payniko.com/problems/1000001",
"title": "Validation failed",
"status": 422,
"detail": "The submitted fields are not valid.",
"code": "1000001",
"correlation_id": "4c7a1e9d-2b3f-4d85-9a6e-0f1b2c3d4e5f",
"errors": {
"customer.email": [
"customer.email must be the payer's real address; placeholder, example or disposable addresses (example.com, test@…, mailinator…) are not accepted."
],
"customer.phone": [
"customer.phone must be the payer's real number; repeated or sequential digit patterns (5555555555, 1234567890) are not accepted."
]
}
}If a request is rejected because of customer, show the payer the reason clearly (for example: enter your own e-mail address or phone number) and let them correct the field before you create the payment again. The messages in errors are in English and intended for your logs and support.
Payniko payment link pages apply the same rules to the e-mail address and phone number the payer enters there, and show the payer the reason for a rejection.
Request body · Example
{
"merchant_order_id": "ORDER-2026-001",
"amount": "100.00",
"currency": "USD",
"webhook_url": "https://merchant.example/webhooks/payments",
"success_url": "https://merchant.example/payment/success",
"fail_url": "https://merchant.example/payment/failure",
"customer": {
"email": "[email protected]",
"phone": "+37061234567"
}
}KEY="$(openssl rand -hex 16)" # store with the order before sending; reuse it after network errors
./payniko.sh POST '/api/payments/hosted' '{
"merchant_order_id": "ORDER-2026-001",
"amount": "100.00",
"currency": "USD",
"webhook_url": "https://merchant.example/webhooks/payments",
"success_url": "https://merchant.example/payment/success",
"fail_url": "https://merchant.example/payment/failure",
"customer": {
"email": "[email protected]",
"phone": "+37061234567"
}
}' "$KEY"import {api, randomUUID} from './payniko.mjs';
const key = randomUUID(); // Store this key with the order before sending; reuse it after network errors.
const payment = await api('POST', '/api/payments/hosted', {
"merchant_order_id": "ORDER-2026-001",
"amount": "100.00",
"currency": "USD",
"webhook_url": "https://merchant.example/webhooks/payments",
"success_url": "https://merchant.example/payment/success",
"fail_url": "https://merchant.example/payment/failure",
"customer": {
"email": "[email protected]",
"phone": "+37061234567"
}
}, key);
console.log(payment);<?php
require __DIR__ . '/payniko.php';
$key = bin2hex(random_bytes(16)); // store with the order; reuse it after network errors
$payment = payniko_api('POST', '/api/payments/hosted', [
"merchant_order_id" => 'ORDER-2026-001',
"amount" => '100.00',
"currency" => 'USD',
"webhook_url" => 'https://merchant.example/webhooks/payments',
"success_url" => 'https://merchant.example/payment/success',
"fail_url" => 'https://merchant.example/payment/failure',
"customer" => [
"email" => '[email protected]',
"phone" => '+37061234567',
],
], $key);
print_r($payment);from payniko import api
import uuid
key = str(uuid.uuid4()) # store with the order; reuse it after network errors
payment = api("POST", "/api/payments/hosted", {
"merchant_order_id": "ORDER-2026-001",
"amount": "100.00",
"currency": "USD",
"webhook_url": "https://merchant.example/webhooks/payments",
"success_url": "https://merchant.example/payment/success",
"fail_url": "https://merchant.example/payment/failure",
"customer": {
"email": "[email protected]",
"phone": "+37061234567"
}
}, key)
print(payment)Response · 201
Returns { "data": Payment } with status: requires_action and next_action.url.
Response · Example
{
"data": {
"payment_id": "LUVEI-PAYN-123456",
"merchant_order_id": "ORDER-2026-001",
"flow": "hosted",
"status": "requires_action",
"amount": "100.00",
"currency": "USD",
"environment": "sandbox",
"next_action": {
"type": "redirect",
"url": "https://payniko.com/l/example-checkout",
"expires_at": "2026-11-02T12:00:00.000Z"
},
"card": null,
"failure": null,
"created_at": "2026-11-01T12:00:00.000Z",
"expires_at": "2026-11-02T12:00:00.000Z",
"completed_at": null
}
}Other responses
200: the same key and body were resent. The original payment is returned and no new payment is created.4099000010: this key has already been used with a different body.4099000039: the first request with this key is still being processed. Resend the same request after a short delay.4099000007: a payment already exists for thismerchant_order_id.4229000005: the amount is outside your account limits;limitscontains the permitted range.4221000001withcustomer,customer.emailorcustomer.phoneinerrors: the contact details are missing or invalid.4221000001,1000020,1000021,9000008,9000038: a field is invalid; details are inerrorsandintegration.violations.
HTTP 422 · 9000005
{
"type": "https://payniko.com/problems/9000005",
"title": "Invalid amount",
"status": 422,
"detail": "The amount is outside the account limits.",
"code": "9000005",
"correlation_id": "6d1f0c2e-8a4b-4c7d-9e21-3f5a7b9c1d20",
"limits": {
"currency_code": "USD",
"min_amount": "25.00",
"max_amount": null
}
}Limits are evaluated in the system main currency (limits.currency_code) after your amount has been converted. max_amount: null means there is no upper limit. The limit values in this example are illustrative.