Create a brand
Add your website or business brand and configure its supported wallets.
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.
https://pay1.urontopay.comhttps://pay1.urontopay.com/api/payment/createhttps://pay1.urontopay.com/api/payment/verifyA complete payment integration follows these four server-side steps.
Add your website or business brand and configure its supported wallets.
Copy the Client ID, Secret Key, and Brand Key from your dashboard.
Send order details to the Create Payment endpoint and redirect to the returned URL.
Verify the transaction ID before marking the order as paid.
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-IDIdentifies the API credential record used by your server.
Example: client_••••••••SECRET-KEYPrivate server-side secret. Never expose it in HTML or JavaScript.
Example: secret_••••••••BRAND-KEYConnects the payment to the selected brand and wallet settings.
Example: brand_••••••••URONTOPAY_BASE_URL=https://pay1.urontopay.comURONTOPAY_CLIENT_ID=YOUR_CLIENT_ID
URONTOPAY_SECRET_KEY=YOUR_SECRET_KEY
URONTOPAY_BRAND_KEY=YOUR_BRAND_KEY
| Header | Value | Required | Notes |
|---|---|---|---|
Content-Type | application/json | Yes | All request bodies are JSON. |
CLIENT-ID | Your Client ID | Yes | Copy from the API Credentials page. |
SECRET-KEY | Your Secret Key | Yes | Keep only on your trusted server. |
BRAND-KEY | Your Brand Key | Yes | Use the key for the brand receiving the payment. |
https://pay1.urontopay.com/api/payment/createCreates a hosted checkout URL. Redirect the customer to the returned payment_url.
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
amount | number | Yes | Payment amount from 0.01 to 1,000,000. | 500 |
success_url | URL | Yes | Customer return URL after successful checkout. | https://shop.example/payment/success |
cancel_url | URL | Yes | Customer return URL after cancellation or failed checkout. | https://shop.example/payment/cancel |
cus_name | string | No | Customer display name. | Customer Name |
cus_email | No | Customer email address. | customer@example.com | |
webhook_url | URL | No | Server-to-server payment notification URL. | https://shop.example/webhooks/urontopay |
metadata | object | No | Your own order or reference data. | {"order_id":"ORDER-1001"} |
return_type | string | No | GET or POST. Default is GET. | GET |
<?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;
<?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'));
}
}
'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'),
],
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));
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"])
<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.
-->
{
"status": 1,
"message": "Payment Link",
"payment_url": "https://pay1.urontopay.com/api/execute/59522f..."
}
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.payment_url after a successful Create request.Use this order of operations so that the customer experience is smooth and your order status remains secure.
order_id inside metadata.https://pay1.urontopay.com/api/payment/verifyPass 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.
| Field | Type | Required | Description |
|---|---|---|---|
transaction_id | string | Yes | The transaction ID returned after checkout. |
<?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.';
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');
}
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);
}
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"))
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"
}'
{
"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"
}
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.
Useful for showing a success, pending, or cancelled page to the customer. Browser parameters are not proof of payment.
Useful for background processing when the customer closes the browser. Verify the transaction before changing order status.
paymentMethod=bkash
transactionId=UP123456
paymentAmount=500
paymentFee=0
status=completed
transactionId, call the Verify endpoint from your server, confirm status === "COMPLETED", compare the amount and order reference, then update the order exactly once.Treat any non-2xx response as unsuccessful. Log the HTTP status and message, but never log the Secret Key.
| HTTP | Meaning | What to check |
|---|---|---|
| 400 | Invalid or missing request data. | Amount range, required URLs, JSON syntax, and transaction ID. |
| 401 | Authentication or plan validation failed. | All three headers, matching account/brand, credential status, and active plan. |
| 404 | Payment or transaction was not found. | Endpoint path and transaction reference. |
| 500 | Unexpected server processing error. | Retry safely, keep the order pending, and contact support if repeated. |
{
"status": 0,
"message": "Client ID, Secret Key and Brand Key are required."
}
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.
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.
Store all credentials in environment variables or a secrets manager.
Use HTTPS for checkout, success, cancel, webhook, and API communication.
Never trust browser return parameters or a webhook payload without API verification.
Compare verified amount and metadata against your own pending order.
Use the transaction ID or order ID as an idempotency guard.
Regenerate a Secret Key immediately if it may have been disclosed.