Daftar isi

Referensi API · v1

Dokumentasi API

API SHOMERCH memakai REST, menerima dan membalas JSON. Satu API key, satu bentuk data, dan satu bentuk webhook — untuk semua penyedia QRIS.

Base URL https://shomerch.web.id/api/v1

Mulai cepat

Empat hal yang perlu disiapkan sebelum request pertama:

  1. Akun dengan paket aktif
  2. Payment gateway tersambung — ShopeePay, GoPay, BCA, DANA, Livin' Merchant, BNI, atau BRI (Dashboard → Payment Gateway). Opsional: Binance Pay (USDT) sebagai metode tambahan
  3. API key (Dashboard → API Keys)
  4. Endpoint webhook, kalau ingin diberi tahu otomatis

Cek dulu apakah semuanya siap:

curl 'https://shomerch.web.id/api/v1/ping' \
  -H 'X-API-Key: str-live-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

Autentikasi

Kirim API key di header X-API-Key pada setiap request. Bisa juga sebagai Authorization: Bearer str-live-….

Jangan pakai API key di browser
Key ini memberi akses penuh ke transaksi akun Anda. Simpan di sisi server — environment variable, bukan di kode JavaScript yang dikirim ke pengunjung.

Kode unik — kenapa Rp10.000 jadi Rp10.081

QRIS statis tidak mengunci nominal, dan portal merchant tidak menyediakan API "cek status invoice". Yang tersedia hanya daftar transaksi masuk. Jadi pencocokan dilakukan lewat nominal.

Supaya dua pesanan tidak tertukar, SHOMERCH menambahkan kode unik 1–99 pada setiap transaksi. Pesanan Rp10.000 menjadi Rp10.081, dan nominal itu dijamin tidak sama dengan pesanan lain yang sedang berjalan di merchant Anda.

Tampilkan total_amount, bukan amount
amount adalah harga produk Anda. total_amount adalah yang harus dibayar pelanggan dan yang terkunci di dalam QR. Kalau Anda menampilkan amount, pelanggan akan bingung melihat nominal berbeda di aplikasi bank.

Selisih kode unik (Rp1–99) tetap masuk ke akun merchant Anda.

Pengecualian: GoPay tanpa kode unik

Untuk merchant GoPay, QR dibuat persis senilai harga (unique_code = 0, total_amount = amount). Sebagai gantinya, satu nominal hanya boleh dipakai satu pesanan aktif per merchant. Selama pesanan Rp10.000 masih menunggu pembayaran, pesanan Rp10.000 berikutnya ditolak dengan 409 NOMINAL_IN_USE beserta error.retry_after (detik) dan header Retry-After. Nominal yang berbeda tetap bisa berjalan bersamaan.

Nominal dilepas begitu pesanan itu lunas. Kalau pesanan kedaluwarsa atau dibatalkan, nominal baru dilepas tiga menit setelah waktu kedaluwarsanya, supaya pembayaran telat dari pembeli sebelumnya tidak melunasi pesanan orang lain.

Binance Pay (USDT) — metode tambahan

Selain QRIS, transaksi bisa dibayar dalam USDT lewat Binance Pay ke akun Binance pribadi Anda. Ini tidak menggantikan QRIS: transaksi tanpa method tetap QRIS seperti biasa, dan Binance hanya dipakai untuk transaksi yang dibuat dengan "method": "binance". Jadi bot Anda bisa menampilkan dua tombol — QRIS dan Binance Pay — dan memanggil endpoint yang sama dengan method yang berbeda.

  1. Sambungkan di Dashboard → Payment Gateway → Binance Pay: API Key + Secret Key (izin Enable Reading saja — key yang bisa trading/withdraw ditolak), Pay ID, dan (opsional) isi QR Binance Pay Anda. Lalu Tes Koneksi.
  2. Panggil GET /me: tampilkan tombol Binance hanya kalau payment_methods memuat "binance".
  3. Buat transaksi dengan "method": "binance" dan amount dalam USDT (maks 2 desimal, minimal 0.1).
  4. Tunjukkan ke pembeli: total_amount USDT, pay_to (Pay ID), dan QR-nya kalau qr_image_url tidak null.
  5. Pembeli mengirim USDT lewat Binance Pay. Lunas terdeteksi dalam ±10 detik, lalu webhook payment.paid dikirim dengan payment_method: "BINANCE_PAY".
curl -X POST 'https://shomerch.web.id/api/v1/transactions' \
  -H 'X-API-Key: str-live-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{ "ref_id": "INV-2026-0002", "method": "binance", "amount": 5 }'
{
  "success": true,
  "data": {
    "id": "trx_4b2d...",
    "ref_id": "INV-2026-0002",
    "method": "binance",
    "currency": "USDT",
    "amount": 5,
    "unique_code": 0,          // 0.01, 0.02, … kalau 5.00 sedang dipakai pesanan lain
    "total_amount": 5,         // ← yang harus dikirim pembeli, PERSIS
    "pay_to": "123456789",    // Binance Pay ID tujuan
    "qr_string": "https://app.binance.com/...",
    "qr_image_url": "https://shomerch.web.id/api/v1/transactions/INV-2026-0002/qr.png",
    "status": "PENDING",
    "expires_at": "2026-09-08T14:08:11+07:00"
  }
}

Nominal: pas dulu, penanda sekecil mungkin

QR Binance Pay pribadi tidak bisa mengunci nominal — pembeli mengetik nominalnya sendiri di aplikasi Binance. Karena itu pesanan pertama dibayar pas (5.00). Kalau ada pesanan lain senominal yang masih menunggu, berikutnya mendapat 5.01, lalu 5.02, dan seterusnya — paling banyak +0.20 USDT. Lebih dari 21 pesanan aktif senominal ditolak dengan 409 UNIQUE_CODE_EXHAUSTED. Seperti GoPay, nominal pesanan yang kedaluwarsa/dibatalkan baru dilepas tiga menit setelah waktu kedaluwarsanya.

Minta pembeli mengirim nominal yang persis sama
5.01 dan 5.00 adalah dua pesanan berbeda. Pembayaran yang kurang, lebih, atau dalam koin lain (bukan USDT) tidak akan terdeteksi otomatis — uangnya tetap masuk ke akun Binance Anda. Tampilkan nominal dengan titik desimal (5.01), persis seperti yang diketik di aplikasi Binance.

Alur lengkap

1. Pelanggan checkout                → POST /transactions
2. Tampilkan QR + total_amount       → qr_string / qr_image_url
3. Pelanggan scan & bayar            → (di aplikasi bank/e-wallet mereka)
4. SHOMERCH mendeteksi (~10 detik)     → status jadi PAID
5. Webhook payment.paid dikirim      → sistem Anda memproses pesanan

   Kalau tidak dibayar sampai expires_at + 3 menit:
   → status jadi EXPIRED, webhook payment.expired dikirim

Cek koneksi

GET/api/v1/ping

Satu-satunya endpoint yang tidak butuh paket aktif. Pakai ini saat memasang integrasi untuk memastikan key benar dan merchant sudah tersambung.

{
  "success": true,
  "data": {
    "pong": true,
    "api_key": { "name": "Bot Telegram", "mode": "live" },
    "plan_active": true,
    "merchant_status": "active",        // atau not_connected / expired / invalid / unverified
    "server_time": "2026-09-08T14:03:11+07:00"
  }
}

Buat transaksi

POST/api/v1/transactions

FieldTipeKeterangan
ref_idstringWajib. Nomor pesanan Anda. Unik per akun — mengirim ulang ref_id yang sama mengembalikan transaksi lama, tidak membuat yang baru.
amountnumberWajib. QRIS: harga dalam rupiah, bilangan bulat, minimal 1000. Binance Pay: harga dalam USDT, maks 2 desimal, minimal 0.1.
methodstringqris (bawaan, boleh tidak dikirim) atau binance. Lihat Binance Pay.
expires_inintegerUmur QR dalam detik. 60–1800, default 300.
customerobject{ name, email } — opsional, hanya untuk catatan Anda.
callback_urlstringWebhook khusus untuk transaksi ini, menimpa endpoint global.
metadataobjectData bebas maks 2 KB. Dikembalikan apa adanya di webhook.
curl -X POST 'https://shomerch.web.id/api/v1/transactions' \
  -H 'X-API-Key: str-live-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{
    "ref_id": "INV-2026-0001",
    "amount": 10000,
    "metadata": { "product_id": 42 }
  }'
const res = await fetch('https://shomerch.web.id/api/v1/transactions', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.SHOMERCH_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    ref_id: 'INV-2026-0001',
    amount: 10000,
    metadata: { product_id: 42 },
  }),
});

const { success, data, error } = await res.json();
if (!success) throw new Error(error.message);

// Tampilkan data.total_amount (10081) dan data.qr_string ke pelanggan.
console.log(data.total_amount, data.qr_string);
$ch = curl_init('https://shomerch.web.id/api/v1/transactions');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'X-API-Key: ' . getenv('SHOMERCH_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'ref_id' => 'INV-2026-0001',
        'amount' => 10000,
    ]),
]);

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

if (empty($res['success'])) {
    throw new Exception($res['error']['message']);
}

$total = $res['data']['total_amount'];  // 10081
$qr    = $res['data']['qr_string'];
import os, requests

r = requests.post(
    'https://shomerch.web.id/api/v1/transactions',
    headers={'X-API-Key': os.environ['SHOMERCH_KEY']},
    json={'ref_id': 'INV-2026-0001', 'amount': 10000},
    timeout=15,
)
body = r.json()

if not body['success']:
    raise RuntimeError(body['error']['message'])

data = body['data']
print(data['total_amount'], data['qr_string'])

Respons 201

{
  "success": true,
  "data": {
    "id": "trx_9f1c8a2e-...",
    "user_id": "usr_3a7e1b4c-...",
    "ref_id": "INV-2026-0001",
    "method": "qris",
    "currency": "IDR",
    "amount": 10000,
    "unique_code": 81,
    "total_amount": 10081,        // ← yang dibayar pelanggan
    "qr_string": "00020101021226610016ID.CO.SHOPEE.WWW...",
    "qr_image_url": "https://shomerch.web.id/api/v1/transactions/INV-2026-0001/qr.png",
    "status": "PENDING",
    "created_at": "2026-09-08T14:03:11+07:00",
    "expires_at": "2026-09-08T14:08:11+07:00"
  }
}

Cek status transaksi

GET/api/v1/transactions/:ref_id

curl 'https://shomerch.web.id/api/v1/transactions/INV-2026-0001' \
  -H 'X-API-Key: str-live-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

Status yang mungkin: PENDING, PAID, EXPIRED, CANCELED, FAILED.

Kalau Anda memakai webhook, tidak perlu polling endpoint ini terus-menerus. Setiap panggilan tetap dihitung sebagai request dan terkena rate limit.

Daftar transaksi

GET/api/v1/transactions

QueryKeterangan
statusFilter status. Salah satu dari PENDING, PAID, EXPIRED, FAILED, CANCELED.
from, toRentang tanggal pembuatan, format YYYY-MM-DD
updated_sinceWaktu ISO-8601. Hanya transaksi yang berubah setelah waktu ini, diurutkan dari yang paling lama. Lihat bagian polling.
pageHalaman, mulai 1
limitBaris per halaman, maks 100 (default 20)

Baris pada endpoint ini tidak menyertakan qr_string dan qr_image_url — ambil satu transaksi lewat GET /transactions/:ref_id kalau Anda membutuhkannya.

{
  "success": true,
  "data": {
    "transactions": [ /* … */ ],
    "next_since": "2026-09-08T14:05:47+07:00",   // penanda polling berikutnya
    "pagination": { "page": 1, "limit": 20, "total": 37, "total_pages": 2 }
  }
}

Batalkan transaksi

POST/api/v1/transactions/:ref_id/cancel

Hanya berlaku untuk transaksi yang masih PENDING. Membatalkan juga melepas nominalnya supaya bisa dipakai pesanan lain.

Gambar QR

GET/api/v1/transactions/:ref_id/qr.png?size=512

Mengembalikan PNG. Parameter size adalah sisi gambar dalam piksel, dijepit ke rentang 120–1024 (default 480). Praktis untuk ditempel langsung di HTML atau dikirim lewat bot:

<img src="https://shomerch.web.id/api/v1/transactions/INV-1/qr.png?size=400">

Transaksi Binance Pay tanpa QR tersimpan tidak punya gambar: qr_image_url-nya null dan endpoint ini membalas 404 QR_NOT_AVAILABLE — tunjukkan pay_to saja.

Endpoint ini tetap butuh header X-API-Key, jadi untuk ditampilkan di browser sebaiknya QR di-render sendiri dari qr_string atau di-proxy lewat server Anda.

Mutasi uang masuk

GET/api/v1/mutations

Riwayat transaksi mentah yang terbaca dari akun merchant Anda — termasuk pembayaran yang tidak berasal dari SHOMERCH. Berguna untuk rekonsiliasi pembukuan.

QueryKeterangan
directionIN (uang masuk) atau OUT. Kosong berarti keduanya.
from, toRentang tanggal, format YYYY-MM-DD (waktu WIB)
pageHalaman, mulai 1
limitBaris per halaman, maks 100 (default 20)
{
  "success": true,
  "data": {
    "mutations": [{
      "provider": "shopeepay",
      "provider_transaction_id": "2609081405…",
      "shopee_transaction_id": "2609081405…",   // nama lama, nilainya sama
      "direction": "IN",
      "amount": 10081,
      "currency": "IDR",              // USDT untuk Binance Pay
      "status": "Berhasil",
      "store_name": "Toko Saya",
      "occurred_at": "2026-09-08T14:05:47+07:00"
    }],
    "pagination": { "page": 1, "limit": 20, "total": 5, "total_pages": 1 }
  }
}

Tiap baris memuat provider (shopeepay, gopay, bca, dana, livin, bni, bri, atau binance) dan provider_transaction_id. Field lama shopee_transaction_id masih dikirim dengan nilai yang sama supaya integrasi yang sudah jalan tidak perlu diubah.

Info akun

GET/api/v1/me

{
  "success": true,
  "data": {
    "user_id": "usr_3a7e...",
    "name": "Toko Saya",
    "plan": { "code": "basic", "name": "Basic", "expires_at": "2026-10-08T..." },
    "quota": { "limit": 50, "used": 38, "remaining": 12, "resets_at": "2026-09-09T00:00:00+07:00" },
    "merchant": { "status": "active", "name": "Toko Saya" },
    "payment_methods": [                     // metode yang siap dipakai SEKARANG
      { "method": "qris", "provider": "shopeepay", "name": "ShopeePay", "currency": "IDR" },
      { "method": "binance", "provider": "binance", "name": "Binance Pay", "currency": "USDT" }
    ]
  }
}

Pakai payment_methods untuk menentukan tombol pembayaran di bot Anda: tombol Binance Pay cukup ditampilkan kalau "binance" ada di daftar ini.

Webhook

Setiap kali status transaksi berubah, kami mengirim POST ke endpoint Anda dengan header berikut:

X-Shomerch-Event      : payment.paid
X-Shomerch-Event-Id   : 6f1b2c8a-...        # idempotency key
X-Shomerch-Timestamp  : 1788677476          # epoch detik
X-Shomerch-Signature  : sha256=<hex>

Payload

{
  "event": "payment.paid",
  "created_at": "2026-09-08T14:05:52+07:00",
  "data": {
    "id": "trx_9f1c...",
    "user_id": "usr_3a7e...",
    "ref_id": "INV-2026-0001",
    "method": "qris",                     // atau "binance"
    "currency": "IDR",                    // atau "USDT"
    "amount": 10000,
    "unique_code": 81,
    "total_amount": 10081,
    "status": "PAID",
    "paid_at": "2026-09-08T14:05:47+07:00",
    "payment_method": "QRIS_SHOPEEPAY",      // BINANCE_PAY untuk Binance
    "metadata": { "product_id": 42 }
  }
}

Event

EventKapan dikirim
payment.paidPembayaran terdeteksi dan cocok
payment.expiredQR kedaluwarsa tanpa pembayaran
payment.canceledDibatalkan lewat API atau dashboard
payment.failedDicadangkan. Status FAILED belum pernah dihasilkan oleh sistem pencocokan, jadi event ini belum pernah terkirim. Boleh Anda tangani sejak sekarang.

Retry

Endpoint Anda harus membalas HTTP 2xx dalam 10 detik. Kalau tidak, kami ulang sampai 6 kali: langsung, 30 detik, 2 menit, 10 menit, 1 jam, 6 jam. Setelah itu ditandai gagal dan bisa dikirim ulang manual dari dashboard.

Proses pesanan Anda setelah membalas 200 — jangan tahan respons sampai pekerjaan berat selesai, atau kami akan menganggapnya timeout dan mengirim ulang.

Verifikasi tanda tangan

Wajib diverifikasi
Tanpa verifikasi, siapa pun yang tahu URL webhook Anda bisa mengirim payment.paid palsu dan mendapat barang tanpa membayar.

Tanda tangan dihitung dari timestamp + "." + body mentah memakai HMAC SHA-256 dengan signing secret Anda. Tiga hal yang harus benar:

  1. Pakai body mentah, bukan hasil parse lalu di-stringify ulang — urutan key bisa berubah dan tanda tangan langsung tidak cocok.
  2. Bandingkan dengan fungsi timing-safe, bukan ===.
  3. Tolak kalau timestamp lebih tua dari 5 menit — ini yang mencegah serangan replay.
const crypto = require('crypto');

// PENTING: ambil body mentah, jangan express.json() untuk route ini.
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const raw       = req.body.toString('utf8');
  const timestamp = req.get('x-shomerch-timestamp');
  const signature = (req.get('x-shomerch-signature') || '').replace('sha256=', '');

  // 1. Tolak yang kedaluwarsa (anti replay)
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return res.status(400).send('timestamp kedaluwarsa');
  }

  // 2. Hitung ulang tanda tangan
  const expected = crypto
    .createHmac('sha256', process.env.SHOMERCH_WEBHOOK_SECRET)
    .update(`${timestamp}.${raw}`)
    .digest('hex');

  // 3. Bandingkan timing-safe
  const a = Buffer.from(signature), b = Buffer.from(expected);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.status(401).send('tanda tangan tidak valid');
  }

  const event = JSON.parse(raw);

  // Balas dulu, proses belakangan — batas waktu kami 10 detik.
  res.sendStatus(200);

  if (event.event === 'payment.paid') {
    // Idempoten: event yang sama bisa datang dua kali.
    prosesPesanan(event.data.ref_id, event.data.total_amount);
  }
});
<?php
$raw       = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_SHOMERCH_TIMESTAMP'] ?? '';
$signature = str_replace('sha256=', '', $_SERVER['HTTP_X_SHOMERCH_SIGNATURE'] ?? '');

// 1. Anti replay
if (abs(time() - (int) $timestamp) > 300) {
    http_response_code(400);
    exit('timestamp kedaluwarsa');
}

// 2. Hitung ulang
$expected = hash_hmac('sha256', $timestamp . '.' . $raw, getenv('SHOMERCH_WEBHOOK_SECRET'));

// 3. Bandingkan timing-safe
if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('tanda tangan tidak valid');
}

$event = json_decode($raw, true);
http_response_code(200);   // balas dulu

if ($event['event'] === 'payment.paid') {
    prosesPesanan($event['data']['ref_id']);
}
import hmac, hashlib, time, os
from flask import Flask, request

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    raw       = request.get_data()               # bytes mentah
    timestamp = request.headers.get('X-Shomerch-Timestamp', '')
    signature = request.headers.get('X-Shomerch-Signature', '').replace('sha256=', '')

    # 1. Anti replay
    if abs(time.time() - int(timestamp or 0)) > 300:
        return 'timestamp kedaluwarsa', 400

    # 2. Hitung ulang
    expected = hmac.new(
        os.environ['SHOMERCH_WEBHOOK_SECRET'].encode(),
        timestamp.encode() + b'.' + raw,
        hashlib.sha256,
    ).hexdigest()

    # 3. Bandingkan timing-safe
    if not hmac.compare_digest(expected, signature):
        return 'tanda tangan tidak valid', 401

    event = request.get_json()
    if event['event'] == 'payment.paid':
        proses_pesanan(event['data']['ref_id'])

    return '', 200

Tanpa webhook — cara polling yang benar

Webhook itu opsional. Kalau aplikasi Anda tidak punya alamat publik — bot Telegram atau WhatsApp yang jalan di laptop, di rumah, atau di belakang NAT — biarkan saja kolom webhook kosong. Tidak ada yang rusak; status transaksi tetap diperbarui, Anda tinggal menanyakannya.

Jangan poll satu per satu
Memanggil /transactions/:ref_id untuk tiap pesanan yang menunggu adalah cara paling boros. Sepuluh pesanan aktif yang dicek tiap 5 detik = 120 request/menit — jauh di atas jatah paket mana pun, dan Anda akan kena RATE_LIMITED.

Cara yang benar: satu panggilan untuk semua pesanan

Pakai updated_since pada endpoint daftar. Satu panggilan mengembalikan semua transaksi yang berubah sejak pengecekan terakhir — berapa pun jumlah pesanan yang sedang berjalan.

QueryKeterangan
updated_sinceWaktu ISO-8601. Hanya transaksi yang berubah setelah waktu ini yang dikembalikan, diurutkan dari yang paling lama.
statusSaring, mis. PAID saja.

Respons memuat next_since — pakai nilai itu apa adanya untuk panggilan berikutnya. Kalau tidak ada yang berubah, nilainya dipantulkan balik, jadi tidak ada yang terlewat.

// Satu loop untuk SELURUH pesanan. 12 request/menit, tetap segitu
// meski ada 200 pesanan berjalan.
let sejak = new Date().toISOString();

setInterval(async () => {
  const url = 'https://shomerch.web.id/api/v1/transactions' +
    '?status=PAID&updated_since=' + encodeURIComponent(sejak);

  const r = await fetch(url, {
    headers: { 'X-API-Key': process.env.SHOMERCH_KEY },
  });
  const { data } = await r.json();

  for (const trx of data.transactions) {
    // Idempoten: simpan ref_id yang sudah diproses, event yang sama
    // bisa muncul lagi kalau penanda waktu mundur.
    if (await sudahDiproses(trx.ref_id)) continue;
    await kirimBarang(trx.ref_id, trx.total_amount);
  }

  sejak = data.next_since;   // penanda untuk putaran berikutnya
}, 5000);
import os, time, requests
from datetime import datetime, timezone

sejak = datetime.now(timezone.utc).isoformat()
sudah = set()

while True:
    r = requests.get(
        'https://shomerch.web.id/api/v1/transactions',
        headers={'X-API-Key': os.environ['SHOMERCH_KEY']},
        params={'status': 'PAID', 'updated_since': sejak},
        timeout=15,
    )
    data = r.json()['data']

    for trx in data['transactions']:
        if trx['ref_id'] in sudah:
            continue
        sudah.add(trx['ref_id'])
        kirim_barang(trx['ref_id'], trx['total_amount'])

    sejak = data['next_since']
    time.sleep(5)
curl 'https://shomerch.web.id/api/v1/transactions?status=PAID&updated_since=2026-09-08T14:00:00%2B07:00' \
  -H 'X-API-Key: str-live-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

# Respons memuat next_since — pakai itu untuk panggilan berikutnya.

Berapa sering boleh poll?

PaketRate limitInterval amanSisa untuk request lain
Basic 30/menit 5 detik (12/menit) 18/menit
Standard 60/menit 3 detik (20/menit) 40/menit
Premium 120/menit 2 detik (30/menit) 90/menit
Berhenti poll saat tidak ada pesanan
Kalau tidak ada transaksi PENDING, tidak ada gunanya terus memanggil. Mulai loop saat pesanan pertama dibuat, hentikan saat semuanya selesai atau kedaluwarsa. Ini menghemat kuota dan membuat paket Basic cukup untuk kebanyakan bot.

Webhook atau polling?

WebhookPolling
Butuh alamat publik + HTTPSYaTidak
Jeda deteksiSeketikaSesuai interval poll
Memakan rate limitTidakYa
Cocok untukWeb/VPSBot di laptop, rumah, di balik NAT

Boleh juga dipakai bersamaan: webhook sebagai jalur utama, polling jarang (mis. tiap 1 menit) sebagai jaring pengaman kalau ada webhook yang tidak sampai.

Kode error

Semua error memakai bentuk yang sama:

{
  "success": false,
  "error": {
    "code": "QUOTA_EXCEEDED",
    "message": "Kuota harian paket Basic (50) sudah habis.",
    "docs": "https://shomerch.web.id/docs#errors"
  }
}
HTTPCodeArtinya & apa yang harus dilakukan
400INVALID_JSONBody request bukan JSON yang valid. Periksa Content-Type dan tanda kutipnya.
401INVALID_API_KEYKey salah atau sudah dicabut. Buat key baru di dashboard.
402SUBSCRIPTION_INACTIVEPaket belum aktif atau habis. Perpanjang di dashboard.
403MERCHANT_NOT_CONNECTEDPayment gateway belum disambungkan, string QRIS statisnya kosong, atau kredensialnya tidak valid. Untuk "method": "binance": Binance Pay belum disambungkan atau belum lolos Tes Koneksi.
403MERCHANT_SESSION_EXPIREDSesi penyedia mati. Ambil ulang kredensialnya dari DevTools portal merchant.
403ACCOUNT_SUSPENDEDAkun dibekukan. Hubungi dukungan.
404TRANSACTION_NOT_FOUNDref_id itu tidak ada di akun Anda. Perhatikan huruf besar-kecilnya.
404QR_NOT_AVAILABLETransaksi Binance Pay tanpa QR tersimpan. Tunjukkan pay_to (Pay ID) ke pembeli.
404NOT_FOUNDEndpoint-nya yang tidak ada — biasanya salah ketik path atau versi.
409TRANSACTION_NOT_PENDINGHanya transaksi PENDING yang bisa dibatalkan.
409UNIQUE_CODE_EXHAUSTED99 slot kode unik untuk nominal itu sedang terpakai semua (Binance Pay: 21 slot, mis. 5.00–5.20). Coba lagi beberapa menit.
409NOMINAL_IN_USEKhusus GoPay: nominal yang sama masih dipakai pesanan lain di merchant Anda. Coba lagi setelah error.retry_after detik (juga di header Retry-After), atau setelah pesanan itu lunas/dibatalkan.
422VALIDATION_ERRORField tidak valid — termasuk method yang tidak dikenal atau nominal USDT lebih dari 2 desimal. Lihat error.details untuk per-field.
429RATE_LIMITEDMelebihi request/menit. Tunggu sesuai header Retry-After.
429QUOTA_EXCEEDEDKuota harian habis. Reset jam 00:00 WIB, atau naikkan paket.
500QRIS_BUILD_FAILEDString QRIS statis merchant tidak bisa diubah jadi QR dinamis. Periksa ulang QRIS statisnya di dashboard.
500INTERNAL_ERRORKesalahan di sisi kami. Laporkan error.request_id yang ikut dikirim.

Setiap respons endpoint yang kena rate limit membawa header berikut, jadi Anda tidak perlu menebak sisa jatah:

X-RateLimit-Limit     : 30        # request per menit untuk paket Anda
X-RateLimit-Remaining : 27        # sisa di jendela berjalan
X-RateLimit-Reset     : 1788677520# epoch detik saat jendela direset
Retry-After           : 12        # hanya saat 429

Batas & kuota

Batas BasicStandardPremium
Transaksi per hari 50100Tanpa batas
Request per menit 3060120
API key 2520
Riwayat tersimpan 30 hari90 hari365 hari

Kuota harian dihitung dari transaksi yang dibuat (bukan yang lunas) dan reset jam 00:00 WIB. Sisa kuota bisa dilihat kapan saja lewat GET /api/v1/me.

Praktik terbaik

  • Simpan total_amount, bukan amount. Itu yang dibayar pelanggan dan yang muncul di mutasi merchant Anda.
  • Perlakukan webhook sebagai idempoten. Pakai X-Shomerch-Event-Id untuk membuang duplikat — retry bisa membuat event yang sama datang dua kali.
  • Pakai ref_id yang benar-benar unik. Nomor pesanan Anda sendiri, bukan angka acak — supaya kalau request timeout, mengirim ulang akan mengembalikan transaksi yang sama, bukan membuat QR kedua.
  • Jangan bergantung pada polling saja. Webhook jauh lebih hemat kuota dan lebih cepat.
  • Tangani MERCHANT_SESSION_EXPIRED di aplikasi Anda. Tampilkan pesan ke admin toko, jangan biarkan pelanggan melihat error mentah.
  • Beri toleransi waktu. QR default hidup 5 menit; deteksi butuh beberapa detik setelah pembayaran. Jangan tutup halaman pembayaran terlalu cepat.
Ada yang kurang jelas atau menemukan kekeliruan di dokumen ini? Kirim ke dukungan lewat dashboard — kami perbaiki.