Developer Documentation
REST PAYMENT API

Integrate Urontopay payments with confidence.

Create a hosted payment link, redirect the customer to checkout, receive the return or webhook notification, and verify the transaction from your server before fulfilling an order.

PHP Laravel Node.js Python HTML AI-readable
Server-side payment URLs
https://pay1.urontopay.com
POSThttps://pay1.urontopay.com/api/payment/create
POSThttps://pay1.urontopay.com/api/payment/verify
Use these URLs only from your backend server. The Create response returns a different payment_url for redirecting the customer.
Request / responseJSON
AuthenticationAll 3 keys required
TransportHTTPS only
GET STARTED

Quick start

01

A complete payment integration follows these four server-side steps.

1

Create a brand

Add your website or business brand and configure its supported wallets.

2

Generate credentials

Copy the Client ID, Secret Key, and Brand Key from your dashboard.

3

Create a payment

Send order details to the Create Payment endpoint and redirect to the returned URL.

4

Verify server-side

Verify the transaction ID before marking the order as paid.

Recommended flowYour frontend talks to your own backend. Your backend talks to Urontopay. Never send the Secret Key to the browser.
AUTHENTICATION

API credentials

02

Every Create and Verify request must include all three credentials in request headers. The three values must belong to the same account and active plan.

CLIENT-ID

Client ID

Identifies the API credential record used by your server.

Example: client_••••••••
SECRET-KEY

Secret Key

Private server-side secret. Never expose it in HTML or JavaScript.

Example: secret_••••••••
BRAND-KEY

Brand Key

Connects the payment to the selected brand and wallet settings.

Example: brand_••••••••
Use placeholders in documentationReplace the sample values below with credentials copied from Dashboard → API Credentials. Do not paste real credentials into public code, screenshots, GitHub, or AI chats.

Store credentials in environment variables

.env
URONTOPAY_BASE_URL=https://pay1.urontopay.comURONTOPAY_CLIENT_ID=YOUR_CLIENT_ID
URONTOPAY_SECRET_KEY=YOUR_SECRET_KEY
URONTOPAY_BRAND_KEY=YOUR_BRAND_KEY

Required headers

HeaderValueRequiredNotes
Content-Typeapplication/jsonYesAll request bodies are JSON.
CLIENT-IDYour Client IDYesCopy from the API Credentials page.
SECRET-KEYYour Secret KeyYesKeep only on your trusted server.
BRAND-KEYYour Brand KeyYesUse the key for the brand receiving the payment.
ENDPOINT

Create a payment link

03
POSThttps://pay1.urontopay.com/api/payment/create

Creates a hosted checkout URL. Redirect the customer to the returned payment_url.

Request body

FieldTypeRequiredDescriptionExample
amountnumberYesPayment amount from 0.01 to 1,000,000.500
success_urlURLYesCustomer return URL after successful checkout.https://shop.example/payment/success
cancel_urlURLYesCustomer return URL after cancellation or failed checkout.https://shop.example/payment/cancel
cus_namestringNoCustomer display name.Customer Name
cus_emailemailNoCustomer email address.customer@example.com
webhook_urlURLNoServer-to-server payment notification URL.https://shop.example/webhooks/urontopay
metadataobjectNoYour own order or reference data.{"order_id":"ORDER-1001"}
return_typestringNoGET or POST. Default is GET.GET

Complete integration examples

create-payment.php
<?php

$baseUrl   = rtrim(getenv('URONTOPAY_BASE_URL'), '/');
$clientId  = getenv('URONTOPAY_CLIENT_ID');
$secretKey = getenv('URONTOPAY_SECRET_KEY');
$brandKey  = getenv('URONTOPAY_BRAND_KEY');

$payload = [
    'amount'       => 500,
    'cus_name'     => 'Customer Name',
    'cus_email'    => 'customer@example.com',
    'success_url'  => 'https://merchant.example/payment/success',
    'cancel_url'   => 'https://merchant.example/payment/cancel',
    'webhook_url'  => 'https://merchant.example/webhooks/urontopay',
    'return_type'  => 'GET',
    'metadata'     => [
        'order_id' => 'ORDER-1001',
    ],
];

$ch = curl_init($baseUrl . '/api/payment/create');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode($payload, JSON_UNESCAPED_SLASHES),
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'CLIENT-ID: ' . $clientId,
        'SECRET-KEY: ' . $secretKey,
        'BRAND-KEY: ' . $brandKey,
    ],
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);

$raw      = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error    = curl_error($ch);
curl_close($ch);

if ($raw === false || $error) {
    http_response_code(502);
    exit('Payment API connection failed: ' . $error);
}

$response = json_decode($raw, true);

if ($httpCode < 200 || $httpCode >= 300 || empty($response['payment_url'])) {
    http_response_code($httpCode ?: 500);
    exit($response['message'] ?? 'Unable to create payment.');
}

header('Location: ' . $response['payment_url'], true, 302);
exit;
PaymentController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

class PaymentController extends Controller
{
    public function create(Request $request)
    {
        $validated = $request->validate([
            'amount' => ['required', 'numeric', 'min:0.01', 'max:1000000'],
            'email'  => ['nullable', 'email'],
        ]);

        $response = Http::acceptJson()
            ->asJson()
            ->withHeaders([
                'CLIENT-ID'  => config('services.urontopay.client_id'),
                'SECRET-KEY' => config('services.urontopay.secret_key'),
                'BRAND-KEY'  => config('services.urontopay.brand_key'),
            ])
            ->connectTimeout(10)
            ->timeout(30)
            ->post(config('services.urontopay.base_url') . '/api/payment/create', [
                'amount'      => $validated['amount'],
                'cus_name'    => $request->user()->name ?? 'Customer',
                'cus_email'   => $validated['email'] ?? null,
                'success_url' => route('payment.success'),
                'cancel_url'  => route('payment.cancel'),
                'webhook_url' => route('payment.webhook'),
                'return_type' => 'GET',
                'metadata'    => ['order_id' => 'ORDER-1001'],
            ]);

        $response->throw();

        return redirect()->away($response->json('payment_url'));
    }
}
config/services.php
'urontopay' => [
    'base_url'  => env('URONTOPAY_BASE_URL', 'https://pay1.urontopay.com'),
    'client_id' => env('URONTOPAY_CLIENT_ID'),
    'secret_key'=> env('URONTOPAY_SECRET_KEY'),
    'brand_key' => env('URONTOPAY_BRAND_KEY'),
],
payment.js (Node 18+)
const baseUrl = process.env.URONTOPAY_BASE_URL.replace(/\/$/, '');

async function createPayment() {
  const response = await fetch(`${baseUrl}/api/payment/create`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'CLIENT-ID': process.env.URONTOPAY_CLIENT_ID,
      'SECRET-KEY': process.env.URONTOPAY_SECRET_KEY,
      'BRAND-KEY': process.env.URONTOPAY_BRAND_KEY,
    },
    body: JSON.stringify({
      amount: 500,
      cus_name: 'Customer Name',
      cus_email: 'customer@example.com',
      success_url: 'https://merchant.example/payment/success',
      cancel_url: 'https://merchant.example/payment/cancel',
      webhook_url: 'https://merchant.example/webhooks/urontopay',
      return_type: 'GET',
      metadata: { order_id: 'ORDER-1001' },
    }),
    signal: AbortSignal.timeout(30000),
  });

  const data = await response.json();

  if (!response.ok || !data.payment_url) {
    throw new Error(data.message || 'Unable to create payment');
  }

  return data.payment_url;
}

createPayment()
  .then((paymentUrl) => console.log(paymentUrl))
  .catch((error) => console.error(error.message));
payment.py
import os
import requests

base_url = os.environ["URONTOPAY_BASE_URL"].rstrip("/")

headers = {
    "Content-Type": "application/json",
    "CLIENT-ID": os.environ["URONTOPAY_CLIENT_ID"],
    "SECRET-KEY": os.environ["URONTOPAY_SECRET_KEY"],
    "BRAND-KEY": os.environ["URONTOPAY_BRAND_KEY"],
}

payload = {
    "amount": 500,
    "cus_name": "Customer Name",
    "cus_email": "customer@example.com",
    "success_url": "https://merchant.example/payment/success",
    "cancel_url": "https://merchant.example/payment/cancel",
    "webhook_url": "https://merchant.example/webhooks/urontopay",
    "return_type": "GET",
    "metadata": {"order_id": "ORDER-1001"},
}

response = requests.post(
    f"{base_url}/api/payment/create",
    json=payload,
    headers=headers,
    timeout=30,
)
response.raise_for_status()

data = response.json()
if not data.get("payment_url"):
    raise RuntimeError(data.get("message", "Unable to create payment"))

print(data["payment_url"])
Never call the payment API directly from browser JavaScript.HTML should submit to your own backend. Your backend then uses one of the server-side examples above.
checkout.html
<form method="POST" action="/payments/create">
  <label for="amount">Amount</label>
  <input
    id="amount"
    type="number"
    name="amount"
    min="0.01"
    step="0.01"
    required
  >

  <label for="email">Email</label>
  <input
    id="email"
    type="email"
    name="email"
    autocomplete="email"
  >

  <button type="submit">Pay securely</button>
</form>

<!--
  /payments/create is your own secure backend route.
  Do not include CLIENT-ID, SECRET-KEY, or BRAND-KEY in this file.
-->

Success response

200 OKContent-Type: application/json
Response
{
  "status": 1,
  "message": "Payment Link",
  "payment_url": "https://pay1.urontopay.com/api/execute/59522f..."
}
What is payment_url?It is the unique hosted checkout address returned for this payment. Do not build it yourself and do not use the Create API endpoint as the customer checkout URL.
1Read the responseYour backend receives payment_url after a successful Create request.
2Redirect the customerSend the customer's browser to that exact URL to complete hosted checkout.
3Verify before fulfilmentAfter return or webhook, verify the transaction ID from your server.
WORKFLOW

Payment flow

04

Use this order of operations so that the customer experience is smooth and your order status remains secure.

1Customer clicks PayYour checkout page submits to your backend.
2Your server creates paymentSend amount, return URLs, metadata, and three headers.
3Redirect to payment URLThe customer completes payment on hosted checkout.
4Verify transactionYour server verifies the returned transaction ID.
Save your local order before redirectingStore the order ID, expected amount, and status as pending. Put only a safe reference such as order_id inside metadata.
ENDPOINT

Verify a transaction

05
POSThttps://pay1.urontopay.com/api/payment/verify

Pass the transaction ID received from the return URL or webhook. Only mark an order as paid when the verified response status is COMPLETED and the verified amount matches your order.

FieldTypeRequiredDescription
transaction_idstringYesThe transaction ID returned after checkout.

Verification examples

verify-payment.php
<?php

$transactionId = $_GET['transactionId'] ?? $_POST['transactionId'] ?? null;
if (!$transactionId) {
    http_response_code(400);
    exit('Missing transaction ID.');
}

$ch = curl_init(rtrim(getenv('URONTOPAY_BASE_URL'), '/') . '/api/payment/verify');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode(['transaction_id' => $transactionId]),
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'CLIENT-ID: ' . getenv('URONTOPAY_CLIENT_ID'),
        'SECRET-KEY: ' . getenv('URONTOPAY_SECRET_KEY'),
        'BRAND-KEY: ' . getenv('URONTOPAY_BRAND_KEY'),
    ],
    CURLOPT_TIMEOUT        => 30,
]);

$raw = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($raw ?: '{}', true);

if ($httpCode === 200 && ($data['status'] ?? null) === 'COMPLETED') {
    // Also compare amount and your metadata/order_id before fulfillment.
    echo 'Payment verified.';
    exit;
}

http_response_code(400);
echo 'Payment is not completed.';
PaymentController.php
public function verify(Request $request)
{
    $transactionId = $request->input('transactionId')
        ?? $request->input('transaction_id');

    abort_unless($transactionId, 400, 'Missing transaction ID.');

    $response = Http::acceptJson()
        ->asJson()
        ->withHeaders([
            'CLIENT-ID'  => config('services.urontopay.client_id'),
            'SECRET-KEY' => config('services.urontopay.secret_key'),
            'BRAND-KEY'  => config('services.urontopay.brand_key'),
        ])
        ->timeout(30)
        ->post(config('services.urontopay.base_url') . '/api/payment/verify', [
            'transaction_id' => $transactionId,
        ]);

    $response->throw();
    $payment = $response->json();

    if (($payment['status'] ?? null) !== 'COMPLETED') {
        return redirect()->route('payment.failed');
    }

    // Compare verified amount and metadata.order_id with your order.
    return redirect()->route('payment.success');
}
verify.js (Node 18+)
async function verifyPayment(transactionId) {
  const baseUrl = process.env.URONTOPAY_BASE_URL.replace(/\/$/, '');

  const response = await fetch(`${baseUrl}/api/payment/verify`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'CLIENT-ID': process.env.URONTOPAY_CLIENT_ID,
      'SECRET-KEY': process.env.URONTOPAY_SECRET_KEY,
      'BRAND-KEY': process.env.URONTOPAY_BRAND_KEY,
    },
    body: JSON.stringify({ transaction_id: transactionId }),
    signal: AbortSignal.timeout(30000),
  });

  const payment = await response.json();
  if (!response.ok) {
    throw new Error(payment.message || 'Verification failed');
  }

  return payment;
}

const payment = await verifyPayment('UP123456');
if (payment.status === 'COMPLETED') {
  console.log('Payment verified', payment);
}
verify.py
import os
import requests

base_url = os.environ["URONTOPAY_BASE_URL"].rstrip("/")
headers = {
    "Content-Type": "application/json",
    "CLIENT-ID": os.environ["URONTOPAY_CLIENT_ID"],
    "SECRET-KEY": os.environ["URONTOPAY_SECRET_KEY"],
    "BRAND-KEY": os.environ["URONTOPAY_BRAND_KEY"],
}

response = requests.post(
    f"{base_url}/api/payment/verify",
    json={"transaction_id": "UP123456"},
    headers=headers,
    timeout=30,
)
response.raise_for_status()
payment = response.json()

if payment.get("status") == "COMPLETED":
    print("Payment verified", payment)
else:
    print("Payment is not completed", payment.get("status"))
Terminal
curl --request POST 'https://pay1.urontopay.com/api/payment/verify' \
  --header 'Content-Type: application/json' \
  --header 'CLIENT-ID: YOUR_CLIENT_ID' \
  --header 'SECRET-KEY: YOUR_SECRET_KEY' \
  --header 'BRAND-KEY: YOUR_BRAND_KEY' \
  --data '{
    "transaction_id": "UP123456"
  }'

Verify response

200 OKUse status, amount, and metadata to reconcile the order.
Response
{
  "cus_name": "Customer Name",
  "cus_email": "customer@example.com",
  "amount": "500.000",
  "transaction_id": "UP123456",
  "metadata": {
    "order_id": "ORDER-1001"
  },
  "payment_method": "bkash",
  "status": "COMPLETED"
}
NOTIFICATIONS

Return URLs and webhook

06

The customer may return to your success_url or cancel_url. When webhook_url is provided, your server may also receive a server-to-server notification.

Browser return

Useful for showing a success, pending, or cancelled page to the customer. Browser parameters are not proof of payment.

Webhook notification

Useful for background processing when the customer closes the browser. Verify the transaction before changing order status.

Typical callback fields

Example payload
paymentMethod=bkash
transactionId=UP123456
paymentAmount=500
paymentFee=0
status=completed
Always verify before fulfillmentRead transactionId, call the Verify endpoint from your server, confirm status === "COMPLETED", compare the amount and order reference, then update the order exactly once.
TROUBLESHOOTING

Errors and responses

07

Treat any non-2xx response as unsuccessful. Log the HTTP status and message, but never log the Secret Key.

HTTPMeaningWhat to check
400Invalid or missing request data.Amount range, required URLs, JSON syntax, and transaction ID.
401Authentication or plan validation failed.All three headers, matching account/brand, credential status, and active plan.
404Payment or transaction was not found.Endpoint path and transaction reference.
500Unexpected server processing error.Retry safely, keep the order pending, and contact support if repeated.
Example error
{
  "status": 0,
  "message": "Client ID, Secret Key and Brand Key are required."
}
BEFORE LAUNCH

Integration testing checklist

08
AI-READY DOCUMENTATION

Integrate with any coding assistant

09

Share the documentation URL or machine-readable files with an AI coding assistant. The files describe authentication, endpoints, fields, responses, and the secure integration workflow.

Prompt for ChatGPT, Claude, Gemini, Copilot, or another AI

Integration prompt
Read https://urontopay.com/openapi.json and https://urontopay.com/ai-integration.md.

Integrate Urontopay into my server-side application.

Requirements:
1. Load URONTOPAY_BASE_URL, URONTOPAY_CLIENT_ID, URONTOPAY_SECRET_KEY, and URONTOPAY_BRAND_KEY from environment variables.
2. Create a backend endpoint that calls POST /api/payment/create.
3. Redirect the customer to the returned payment_url.
4. Create success, cancel, and webhook handlers.
5. Extract transactionId and call POST /api/payment/verify from the server.
6. Mark the order paid only when status is COMPLETED and the verified amount and metadata.order_id match the local order.
7. Make webhook processing idempotent.
8. Never expose or log the Secret Key in browser code, mobile code, repositories, screenshots, or responses.
9. Include clear error handling, timeouts, and secure production-ready code.
PRODUCTION

Security checklist

10
Keep secrets server-side

Store all credentials in environment variables or a secrets manager.

Use HTTPS everywhere

Use HTTPS for checkout, success, cancel, webhook, and API communication.

Verify every payment

Never trust browser return parameters or a webhook payload without API verification.

Reconcile amount and order

Compare verified amount and metadata against your own pending order.

Handle duplicate notifications

Use the transaction ID or order ID as an idempotency guard.

Rotate exposed credentials

Regenerate a Secret Key immediately if it may have been disclosed.

Copied to clipboard