Developer Hub & REST API v1 Reference

Dokumentasi Teknis Solosmartpay

Panduan lengkap integrasi pembayaran QRIS, Bank Virtual Account, E-Wallet, dan Retail: mulai dari autentikasi token, contoh kode multi-bahasa, kamus field JSON, hingga verifikasi signature Webhook HMAC-SHA256.

Base Endpoint API https://solosmartpay.com/v1
Header Autentikasi X-API-Token
ssp_…
Keamanan Webhook HMAC-SHA256
Signed + Retry 8x
Rate Limit & Format 120 req / menit (default)
JSON
01 / ARSITEKTUR & MODE CHECKOUT

Ringkasan Alur & Pilihan Mode Integrasi

SoloSmartPay menyediakan REST API berbasis JSON yang memungkinkan sistem Anda membuat tagihan QRIS dinamis maupun Virtual Account/E-Wallet, memantau pelunasan secara otomatis, dan menerima notifikasi instan melalui Webhook bertanda tangan kriptografi.

1 · CREATE INVOICE

Nominal + Kode Unik

Backend Anda memanggil POST /v1/qris dengan Idempotency-Key untuk menerima quote QRIS, lalu mengonfirmasi quote_id dengan key baru untuk provisioning.

2 · AUTO-RECONCILE

Pencocokan Mutasi

Pelanggan memindai QRIS dari e-wallet atau m-banking apa pun. Sistem verifikasi otomatis mendeteksi dana masuk yang cocok dengan total_amount secara otomatis dalam hitungan detik.

3 · WEBHOOK & LEDGER

Saldo & Notifikasi

Saldo bersih langsung dikreditkan ke ledger toko Anda dan event payment.paid dikirim ke endpoint Webhook HTTPS Anda.

Mode A · Hosted Checkout (Direkomendasikan) Tanpa Buat UI

Setelah provisioning_status menjadi ready, arahkan pelanggan ke data.checkout_url untuk QRIS atau data.payment_url untuk Bank VA/E-Wallet/Retail.

Mode B · Direct / White-Label UI Custom UI

Untuk QRIS yang sudah siap, gunakan data.qris_string bila Anda perlu merender kode QR sendiri. Wajib: tampilkan nilai data.total_amount dengan jelas agar pelanggan membayar nominal tepat beserta kode uniknya.

API v1 Asinkron (Direkomendasikan untuk Produksi)

Gunakan POST /v1/payments atau POST /v1/qris dengan header Idempotency-Key. Invoice yang diprovisikan mengembalikan 202 Accepted, data, dan status_url; tunggu sampai provisioning_status=ready. QRIS pertama langsung mengembalikan quote; konfirmasi memakai quote_id dan key baru.

02 / AUTENTIKASI, SCOPES & ENDPOINT

Autentikasi Token, Scopes & Daftar Endpoint

API Base URL https://solosmartpay.com/v1. Buat kredensial di menu Developer / API & Webhook → API Tokens, lalu kirimkan melalui header X-API-Token: ssp_… atau Authorization: Bearer ssp_….

qris:write

Izin untuk membuat quote atau provisioning QRIS (POST /v1/qris).

payments:write

Izin untuk mengantrekan tagihan Bank Virtual Account, E-Wallet, dan Retail (POST /v1/payments).

read

Izin membaca status pembayaran & daftar metode aktif (GET /v1/*).

Method Endpoint Path Scope Wajib Fungsi & Keterangan
POST /v1/qris qris:write Membuat quote QRIS atau mengantrekan provisioning dari quote_id.
GET /v1/qris/{id} read Mengecek detail & status pembayaran QRIS (pending, paid, expired).
GET /v1/payment-methods read Mengambil daftar metode Bank VA, E-Wallet, dan Retail yang aktif beserta biaya & batas nominal.
POST /v1/payments payments:write Mengantrekan tagihan Bank Virtual Account / E-Wallet / Retail; respons 202 menyertakan status_url.
GET /v1/payments/{id} read Mengecek status pembayaran tagihan Bank VA / E-Wallet / Retail berdasarkan ID publik.
03 / DYNAMIC QRIS API

Membuat QRIS Dinamis & Kamus Field Response

Panggil POST https://solosmartpay.com/v1/qris dengan amount, header Idempotency-Key, dan reference opsional. Request pertama langsung mengembalikan quote. Untuk menerbitkan QRIS, kirim ulang amount dan quote_id dengan key baru; responsnya 202 dengan status_url. Poll URL tersebut sampai provisioning_status menjadi ready sebelum menggunakan detail pembayaran.

curl -X POST https://solosmartpay.com/v1/qris   -H "X-API-Token: ssp_live_xxxxxxxxxxxxxxxx"   -H "Idempotency-Key: order-123-quote"   -H "Content-Type: application/json"   -d '{
    "amount": 50000,
    "reference": "ORDER-123"
  }'
use Illuminate\Support\Facades\Http;

$response = Http::withHeaders([
    'X-API-Token' => config('services.solosmartpay.token'),
    'Idempotency-Key' => 'order-123-quote',
])->post('https://solosmartpay.com/v1/qris', [
    'amount'    => 50000,
    'reference' => 'ORDER-123',
]);

$quote = $response->throw()->json('data');
// Kirim quote_id dengan key baru untuk provisioning, lalu poll status_url.
const response = await fetch('https://solosmartpay.com/v1/qris', {
  method: 'POST',
  headers: {
    'X-API-Token': process.env.SSP_API_TOKEN,
    'Idempotency-Key': 'order-123-quote',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ amount: 50000, reference: 'ORDER-123' }),
});

const { data } = await response.json();
console.log('Total Bayar:', data.total_amount, 'Checkout URL:', data.checkout_url);
import os, requests

resp = requests.post(
    "https://solosmartpay.com/v1/qris",
    headers={"X-API-Token": os.environ["SSP_API_TOKEN"], "Idempotency-Key": "order-123-quote"},
    json={"amount": 50000, "reference": "ORDER-123"},
    timeout=10,
)
resp.raise_for_status()
data = resp.json()["data"]
print("Bayar tepat Rp", data["total_amount"], "di", data["checkout_url"])
body := strings.NewReader(`{"amount":50000,"reference":"ORDER-123"}`)
req, _ := http.NewRequest("POST", "https://solosmartpay.com/v1/qris", body)
req.Header.Set("X-API-Token", os.Getenv("SSP_API_TOKEN"))
req.Header.Set("Idempotency-Key", "order-123-quote")
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
Provisioning response — POST /v1/qris dengan quote_id
{
  "data": {
    "id": "9d2f6c1e-5b8a-4c1e-9f0a-2b7d3e4f5a6b",
    "provisioning_status": "queued"
  },
  "status_url": "https://solosmartpay.com/v1/qris/9d2f6c1e-5b8a-4c1e-9f0a-2b7d3e4f5a6b"
}
Field Respons Tipe Data Penjelasan untuk Developer
id string (UUID) ID publik transaksi di SoloSmartPay. Simpan di database Anda untuk memanggil GET /v1/qris/{id}.
reference string | null ID order/invoice dari sistem Anda yang dikirim saat pembuatan tagihan.
status string Status tagihan: pending (menunggu pembayaran), paid (lunas), atau expired (melewati 15 menit).
redirect_url string | null URL https:// di toko Anda yang dikirim saat pembuatan tagihan. Kalau diisi, Hosted Checkout (checkout_url) mengalihkan pelanggan ke sana otomatis begitu status jadi paid.
amount & unique_code integer amount adalah nominal pokok; sampai Rp500.000, unique_code adalah kode 3 digit (1–999) untuk rekonsiliasi otomatis dan fee SoloSmartPay flat Rp300. Di atas Rp500.000 nilainya 0 dan fee total 0,5%.
total_amount integer Nominal tepat yang wajib dibayar pelanggan: amount + unique_code. Di atas Rp500.000 pelanggan membayar nominal tepat tanpa angka unik.
fee & net_amount integer fee adalah potongan biaya platform; net_amount adalah saldo bersih yang masuk ke akun toko Anda.
provisioning_status string Status penerbitan invoice. Gunakan detail pembayaran hanya setelah nilainya ready.
status_url (top-level) string URL tingkat atas pada respons 202 untuk polling status provisioning.
04 / VA, E-WALLET & RETAIL

Pembayaran Bank Virtual Account, E-Wallet & Retail

Selain QRIS, Anda dapat menerima pembayaran melalui Virtual Account Bank, E-Wallet, dan Gerai Retail. Ambil daftar kode metode yang aktif melalui GET /v1/payment-methods, lalu antrekan tagihan ke POST /v1/payments dengan Idempotency-Key. Respons 202 menyertakan status_url untuk dipoll. Pilih kode seperti BC untuk mengunci metode, atau gunakan all agar pelanggan memilih dari metode aktif di checkout.

1. Cek Metode Aktif (GET /v1/payment-methods) & 2. Antrekan Tagihan (POST /v1/payments)
# 1. Ambil daftar metode pembayaran yang aktif untuk toko Anda
curl -X GET https://solosmartpay.com/v1/payment-methods   -H "X-API-Token: ssp_live_xxxxxxxxxxxxxxxx"

# 2. Buat tagihan Bank VA / E-Wallet / Retail (min. Rp 10.000)
curl -X POST https://solosmartpay.com/v1/payments   -H "X-API-Token: ssp_live_xxxxxxxxxxxxxxxx"   -H "Idempotency-Key: order-va-77-create"   -H "Content-Type: application/json"   -d '{
    "amount": 150000,
    "method": "BC",
    "reference": "ORDER-VA-77"
  }'

// Respons 202 Accepted:
{
  "data": {
    "id": "3c1f8a20-9b12-4e33-8a11-9c8b7a6f5e4d",
    "provisioning_status": "queued"
  },
  "status_url": "https://solosmartpay.com/v1/payments/3c1f8a20-9b12-4e33-8a11-9c8b7a6f5e4d"
}
05 / WEBHOOK SECURITY

Verifikasi Webhook Merchant (HMAC-SHA256)

Daftarkan URL HTTPS (wajib port 443) di dashboard (Developer Hub → Webhooks). Setiap kali pembayaran lunas (payment.paid) atau kedaluwarsa (invoice.expired), SoloSmartPay mengirim notifikasi HTTP POST dengan jaminan pengiriman at-least-once dan retry otomatis hingga 8 kali. Verifikasi signature HMAC-SHA256 dan balas dengan kode HTTP 2xx dalam waktu ≤ 8 detik.

HTTP Header Webhook Contoh Nilai Kegunaan
X-SoloSmartPay-Signature sha256=8f92a3c1… Signature HMAC-SHA256 dari timestamp + "." + raw_json_body menggunakan Signing Secret Anda.
X-SoloSmartPay-Timestamp 1758945792 Unix epoch detik saat webhook dikirim. Tolak jika selisih waktu > 300 detik untuk mencegah replay attack.
X-SoloSmartPay-Event payment.paid Jenis event: payment.paid, invoice.expired, atau webhook.test.
X-SoloSmartPay-Event-ID evt_3f1c9a… ID unik event. Simpan di database Anda sebagai kunci deduplikasi (idempotency) jika terjadi retry.
X-SoloSmartPay-Delivery-ID dlv_9a8b7c… ID unik untuk setiap percobaan pengiriman HTTP.
Rumus Verifikasi Signature & Jadwal Retry Otomatis
X-SoloSmartPay-Signature = "sha256=" + HMAC_SHA256(webhook_secret, timestamp + "." + raw_json_body)

Exponential Backoff (Maks. 8x Percobaan): Jika server Anda gagal merespons 2xx, pengiriman diulang otomatis dengan jeda 10 detik → 1 menit → 5 menit → 30 menit → 2 jam → 12 jam → 24 jam.

Constant-Time Signature Check
$rawBody   = $request->getContent();
$timestamp = (string) $request->header('X-SoloSmartPay-Timestamp');
$signature = (string) $request->header('X-SoloSmartPay-Signature');
$eventId   = (string) $request->header('X-SoloSmartPay-Event-ID');

// 1. Tolak jika selisih waktu > 300 detik (mencegah replay attack)
if (abs(time() - (int) $timestamp) > 300) {
    abort(401, 'Timestamp expired');
}

// 2. Hitung ulang HMAC-SHA256 menggunakan signing secret webhook Anda
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, config('services.solosmartpay.webhook_secret'));

if (!hash_equals($expected, $signature)) {
    abort(401, 'Invalid webhook signature');
}

// 3. Deduplikasi berdasarkan $eventId, lalu tandai pesanan LUNAS
$event = json_decode($rawBody, true);
if (($event['type'] ?? '') === 'payment.paid') {
    Order::where('ref', $event['data']['reference'])->update(['status' => 'paid']);
}
return response()->json(['ok' => true]);
const crypto = require('crypto');

app.post('/webhook/solosmartpay', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.header('X-SoloSmartPay-Timestamp') || '';
  const signature = req.header('X-SoloSmartPay-Signature') || '';
  const rawBody   = req.body.toString('utf8');

  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return res.status(401).json({ error: 'Timestamp expired' });
  }

  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.SSP_WEBHOOK_SECRET)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  if (expected.length !== signature.length ||
      !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(rawBody);
  // event.type === 'payment.paid' → tandai pesanan event.data.reference lunas
  return res.status(200).json({ received: true });
});
import hmac, hashlib, time

def verify_webhook(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:
        return False
    signed_payload = timestamp.encode("utf-8") + b"." + raw_body
    expected = "sha256=" + hmac.new(secret.encode("utf-8"), signed_payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
mac := hmac.New(sha256.New, []byte(os.Getenv("SSP_WEBHOOK_SECRET")))
mac.Write([]byte(timestamp + "." + string(rawBody)))
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(signature)) {
    http.Error(w, "Invalid webhook signature", http.StatusUnauthorized)
    return
}
06 / ERROR HANDLING

Kode Status HTTP & Penanganan Error

Semua respons error mengembalikan objek JSON dengan properti message (dan errors untuk validasi field HTTP 422).

HTTP Status Arti Penyebab & Cara Mengatasi
200 / 201 OK / Created Tagihan baru berhasil dibuat (201) atau tagihan idempoten / cek status berhasil diambil (200).
401 Unauthorized Token tidak dikirim, salah, sudah dicabut/kedaluwarsa, atau akun toko sedang dibekukan.
403 Forbidden IP server pemanggil tidak terdaftar pada Whitelist IP token, atau token tidak memiliki scope yang sesuai (qris:write / payments:write / read).
404 Not Found ID transaksi (id) tidak ditemukan atau bukan milik toko dari token yang digunakan.
409 Conflict Nilai reference sudah dipakai oleh tagihan aktif dengan nominal atau metode yang berbeda. Gunakan reference unik untuk setiap order.
422 Unprocessable Validasi gagal (mis. amount di luar batas) atau verifikasi KYC toko belum disetujui.
429 Rate Limited Melebihi batas rate limit yang dikonfigurasi per token. Periksa header X-RateLimit-Limit dan tunggu sesuai header Retry-After.
07 / BIAYA, WITHDRAW & CHECKLIST GO-LIVE

Biaya Transaksi, Withdraw & Checklist Produksi

Biaya transaksi dipotong otomatis dari dana yang masuk ke saldo toko; pelanggan cukup membayar sebesar total_amount. Penarikan dana (Withdraw) diajukan dari dashboard ke rekening bank terdaftar.

WD < Rp 1.000.000 Rp 1.500

Biaya transfer bertingkat sesuai nominal penarikan. Minimal penarikan Rp10.000.

WD ≥ Rp 1.000.000 Rp 1.700

Biaya transfer per penarikan untuk nominal di atas batas tarif.

01. Simpan API Token di Server & Aktifkan Whitelist IP: Jangan pernah menaruh ssp_… di kode frontend/mobile. Isi daftar IP server produksi Anda saat membuat token.
02. Tampilkan total_amount dengan Tegas: Jika Anda menggunakan Custom UI (bukan checkout_url), pastikan pelanggan membayar tepat nominal + kode unik agar pembayaran otomatis cocok.
03. Wajib Verifikasi Signature Webhook & Deduplikasi: Validasi X-SoloSmartPay-Signature menggunakan raw request body dan simpan X-SoloSmartPay-Event-ID agar order tidak diproses ganda saat terjadi retry.
08 / MERCHANT PANEL & STORE SETTINGS

Panduan Pengaturan Toko, Metode Pembayaran & Keamanan

Setiap akun SoloSmartPay mendukung hingga 10 toko terpisah. Masing-masing toko memiliki saldo ledger, status KYC, pilihan metode pembayaran (Bank VA, E-Wallet, Retail), rekening pencairan, API Token, dan endpoint Webhook sendiri.

A · VERIFIKASI KYC & MULTI-TOKO

Aktivasi Penerimaan Pembayaran

Di menu Toko Anda → Verifikasi KYC:
1. Lengkapi profil usaha (Perorangan atau Badan Usaha), alamat, dan kategori bisnis.
2. Unggah foto KTP, Selfie memegang KTP, serta NPWP (opsional untuk perorangan).
3. Setelah disetujui (maks. 1×24 jam kerja), toko Anda langsung dapat membuat tagihan QRIS/VA/E-Wallet dan mencairkan dana.

B · PENGELOMPOKAN METODE PEMBAYARAN

Bank VA, E-Wallet & Gerai Retail

Di menu Toko Anda → Pengaturan Toko → Metode Pembayaran:
1. Aktifkan saklar Terima semua metode aktif atau pilih metode spesifik per kelompok (Bank Virtual Account, E-Wallet, dan Gerai Retail). QRIS tersedia sebagai jalur pembayaran terpisah.
2. Endpoint GET /v1/payment-methods otomatis menyesuaikan daftar metode sesuai pengaturan toko Anda.

Menu Panel Merchant Fungsi & Praktik Keamanan Terbaik
Terima Pembayaran
/qris-terminal
Membuat link pembayaran tanpa koding untuk QRIS, Bank VA, E-Wallet, maupun Retail. Untuk QRIS, layar otomatis berubah menjadi centang hijau saat pembayaran masuk.
Rekening & Withdraw
/withdraw
Mengatur rekening bank pencairan (nama pemilik rekening wajib sesuai identitas KYC) dan mengajukan penarikan dana Reguler (hari kerja) maupun Instan (diprioritaskan ≤ 30 menit).
Developer Hub (API & Webhook)
/developer
Membuat API Token (ssp_…) dengan pembatasan Scopes (qris:write, payments:write, read), IP/CIDR Whitelist server produksi, serta menguji pengiriman Webhook HMAC-SHA256.
Keamanan Akun & 2FA
/settings
Mengaktifkan autentikasi dua faktor (TOTP 2FA via Google Authenticator / Authy), menyimpan 8 recovery codes, serta memantau dan mengakhiri sesi perangkat yang sedang login.
Hubungi CS Kami via WhatsApp