OtoPay
Developer documentation

Integrasi Pembayaran OtoPay

Panduan lengkap untuk menerima pembayaran QRIS di website Anda melalui REST API.

BASE URL http://otopay.click
REST APIJSONBearer Auth
Mulai dalam 5 menit

Alur integrasi pembayaran

Gunakan API key hanya dari backend website Anda. Browser customer akan diarahkan ke payment_url yang dibuat oleh OtoPay.

01Siapkan akunAktifkan paket dengan API access.
02Simpan API keyAmbil dari Dashboard → API.
03Buat invoicePOST dari server Anda.
04Terima pembayaranRedirect atau cek status.
Jangan pernah menaruh API key di JavaScript browser. API key adalah password merchant. Simpan di environment variable server, secret manager, atau database backend Anda. Endpoint pada halaman customer cukup menerima payment_url dari backend Anda.
1. Authentication

Autentikasi API

Semua request ke /api/v1/* wajib menggunakan API key merchant pada header Authorization.

BASE URL http://otopay.click

Gunakan URL ini, bukan URL lokal atau URL dashboard. Base URL otomatis mengikuti subfolder instalasi.

API KEY oto_pg_••••••••

Dashboard → API & Integrasi. Jangan bagikan key ini kepada customer.

HTTP headers
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

Pastikan production memakai HTTPS. Endpoint API mengizinkan CORS, tetapi CORS bukan pengganti keamanan API key.

POST /api/v1/create-invoice

Membuat invoice QRIS

Endpoint ini membuat satu invoice, menghasilkan QRIS dinamis, dan mengembalikan halaman pembayaran yang siap dibuka customer.

Request body

JSON
{
  "amount": 150000,
  "description": "Premium Membership",
  "external_id": "ORDER-20260615-001",
  "customer_name": "Budi Santoso",
  "customer_email": "budi@example.com"
}

Response berhasil

JSON · 200
{
  "success": true,
  "data": {
    "invoice_id": 42,
    "external_id": "ORDER-20260615-001",
    "amount": 150000,
    "status": "PENDING",
    "payment_url": "https://domain-anda.com/pay/42",
    "qris_image": "data:image/png;base64,...",
    "expires_at": "2026-06-16T10:00:00.000Z",
    "created_at": "2026-06-15T10:00:00.000Z"
  }
}
FieldTipeKeterangan
amountintegerWajib. Nominal dalam Rupiah tanpa desimal. Harus lebih dari 0 dan tidak melebihi batas paket.
descriptionstringOpsional. Deskripsi yang tampil di halaman pembayaran.
external_idstringOpsional. ID order di sistem Anda untuk rekonsiliasi dan webhook.
customer_namestringOpsional. Nama customer yang ditampilkan.
customer_emailstringOpsional. Email customer untuk disimpan pada invoice.
Setelah invoice dibuat: simpan invoice_id, external_id, dan payment_url di database Anda. Gunakan external_id yang unik dan stabil untuk retry; OtoPay mengembalikan invoice yang sama bila ID tersebut diulang. Jangan membuat invoice baru setiap kali customer me-refresh halaman.
2. Customer flow

Redirect customer ke halaman pembayaran

Cara paling aman dan kompatibel lintas perangkat adalah mengarahkan customer ke payment_url. Halaman OtoPay sudah menangani QRIS, e-wallet, mobile banking, dan polling status.

Browser → backend Anda
// Endpoint di bawah adalah endpoint backend website Anda,
// bukan endpoint OtoPay. Backend sudah menyimpan invoice_id.

const response = await fetch('/api/orders/ORDER-20260615-001/payment-url', {
  credentials: 'same-origin'
});
const data = await response.json();

if (!response.ok || !data.payment_url) {
  throw new Error(data.error || 'Payment URL tidak tersedia');
}

window.location.assign(data.payment_url);
Alternatif: jika ingin menampilkan QRIS sendiri, gunakan nilai data.qris_image sebagai <img src="...">. Pastikan customer tetap diarahkan ke payment_url atau menggunakan alur status yang benar.
POST /api/v1/check-status

Cek status pembayaran

Gunakan endpoint ini dari backend atau worker. Panggil secara berkala hanya jika webhook belum tersedia. OtoPay akan mengecek status ke gateway dan menandai invoice sebagai PAID ketika berhasil. Invoice PENDING yang melewati expires_at otomatis berubah menjadi BATAL dan tidak dapat dibayar lagi.

Request

JSON
{
  "invoice_id": 42
}

Pending

JSON · 200
{
  "success": true,
  "data": {
    "invoice_id": 42,
    "status": "PENDING",
    "amount": 150000
  }
}

Status sudah dibayar

JSON · 200
{
  "success": true,
  "data": {
    "invoice_id": 42,
    "status": "PAID",
    "amount": 150000,
    "paid_at": "2026-06-15T10:05:30.000Z"
  }
}
Node.js backend worker
// Jalankan dari backend/worker, jangan menaruh API key di browser.
const API_BASE = process.env.OTOPAY_BASE_URL;
const API_KEY = process.env.OTOPAY_API_KEY;

const response = await fetch(API_BASE + '/api/v1/check-status', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ' + API_KEY,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ invoice_id: 42 })
});

const result = await response.json();
if (result.success && result.data.status === 'PAID') {
  // baru fulfils order di sistem Anda
  await markOrderAsPaid(result.data.invoice_id, result.data.paid_at);
}
Jangan menganggap redirect customer sebagai bukti pembayaran. Finalisasi order hanya setelah menerima status: "PAID" dari webhook atau check-status.
3. Realtime callback

Webhook invoice paid

Aktifkan URL webhook di Dashboard → Settings. Paket merchant harus mendukung webhook. OtoPay mengirim POST JSON ketika invoice berubah menjadi lunas.

Webhook payload
{
  "event": "invoice.paid",
  "data": {
    "invoice_id": 42,
    "external_id": "ORDER-20260615-001",
    "amount": 150000,
    "paid_at": "2026-06-15T10:05:30.000Z"
  }
}

Contoh receiver PHP

PHP webhook receiver
<?php
// Endpoint ini harus ada di backend Anda.
// Contoh: https://website-anda.com/webhooks/otopay

$payload = json_decode(file_get_contents('php://input'), true);

if (($payload['event'] ?? '') !== 'invoice.paid') {
    http_response_code(200);
    exit;
}

$invoiceId = (int) ($payload['data']['invoice_id'] ?? 0);
$externalId = (string) ($payload['data']['external_id'] ?? '');

if ($invoiceId < 1 || $externalId === '') {
    http_response_code(400);
    exit;
}

// Verifikasi kembali ke OtoPay sebelum akhirnya fulfill order.
// Ganti helper ini dengan request POST /api/v1/check-status di backend Anda.
// Pastikan proses ini idempotent berdasarkan invoice_id / external_id.
verifyInvoiceWithOtoPay($invoiceId);

// Balas 2xx segera agar webhook tidak dianggap gagal.
http_response_code(200);
echo 'OK';
Catatan keamanan: payload webhook saat ini tidak membawa signature HMAC terpisah. Endpoint receiver wajib memakai HTTPS, memvalidasi event, membatasi ukuran payload, dan memverifikasi invoice melalui check-status sebelum fulfill order. Lakukan proses secara idempotent berdasarkan invoice_id.
4. Copy & paste

Contoh integrasi siap pakai

Gunakan contoh di bawah sebagai titik awal. Ganti `YOUR_API_KEY`, dan simpan key melalui environment variable pada production.

PHP cURL — create invoice dari server

PHP
<?php
$apiBase = 'http://otopay.click';
$apiKey  = getenv('OTOPAY_API_KEY') ?: 'YOUR_API_KEY';

$ch = curl_init($apiBase . '/api/v1/create-invoice');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'amount' => 150000,
        'description' => 'Premium Membership',
        'external_id' => 'ORDER-' . time(),
        'customer_name' => 'Budi Santoso',
    ]),
]);

$result = json_decode(curl_exec($ch), true);
curl_close($ch);

if (empty($result['success'])) {
    http_response_code(502);
    exit(json_encode(['error' => $result['error'] ?? 'Gagal membuat invoice']));
}

// Simpan invoice_id di database Anda, lalu kirim payment_url ke browser.
echo json_encode([
    'invoice_id' => $result['data']['invoice_id'],
    'payment_url' => $result['data']['payment_url'],
]);

Node.js / Express — create invoice dari server

JavaScript server
const API_BASE = process.env.OTOPAY_BASE_URL || 'http://otopay.click';
const API_KEY = process.env.OTOPAY_API_KEY;

const response = await fetch(API_BASE + '/api/v1/create-invoice', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ' + API_KEY,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    amount: 150000,
    description: 'Premium Membership',
    external_id: 'ORDER-' + Date.now(),
    customer_name: 'Budi Santoso'
  })
});

const result = await response.json();
if (!response.ok || !result.success) {
  throw new Error(result.error || 'Gagal membuat invoice');
}

// Kirim payment_url ini ke frontend Anda.
console.log(result.data.invoice_id, result.data.payment_url);
Minimum production setup: PHP 8+ dengan cURL atau Node.js 18+, API key di secret/environment variable, HTTPS, database untuk menyimpan invoice_id, dan handler webhook.
5. Reference

Error codes & troubleshooting

Response error selalu berbentuk { "success": false, "error": "..." }. Jangan hanya membaca HTTP status; selalu periksa success.

HTTPPenyebabCara menangani
400Amount invalid atau melewati batas paket.Kirim integer Rupiah dan cek maxAmountPerInvoice.
401API key tidak ada/tidak valid.Periksa header Bearer dan key di Dashboard.
403Paket nonaktif, API access tidak tersedia, atau kuota habis.Aktifkan paket yang sesuai dan cek usage invoice.
404Invoice tidak ditemukan atau milik merchant lain.Simpan invoice_id saat create invoice.
502/503Gateway QRIS belum dikonfigurasi atau sedang bermasalah.Hubungi admin/operator gateway dan coba lagi dengan backoff.
Go live checklist

Checklist sebelum rilis

✓

Paket aktif dan memiliki canApiAccess.

✓

API key hanya berada di backend/secret manager.

✓

Setiap order punya external_id yang unik.

✓

Customer diarahkan ke payment_url yang dikembalikan API.

✓

Webhook handler melakukan verifikasi dan idempotency.

✓

Order baru di-fulfill setelah status PAID.