Dokumentasi API
Salin perintah ini ke ChatGPT, Claude, atau Cursor. Versi Markdown halaman ini dibuat agar mudah dibaca AI.
Baca dokumentasi StreadPay di https://streadpay.com/docs.md lalu tambahkan pembayaran QRIS ke bot/aplikasi saya: buat tagihan saat pembeli checkout, kirim QR ke pembeli, dan tandai pesanan lunas dari webhook payment.paid dengan verifikasi signature.
Terima pembayaran QRIS dari aplikasi, website, atau bot kamu lewat satu REST API.
- Base URL:
https://streadpay.com/v1 - Format: JSON, UTF-8
- Nominal: rupiah, bilangan bulat (tanpa desimal).
10000= Rp10.000 - Waktu: ISO 8601 UTC, contoh
2026-10-31T15:30:00Z - Versi Markdown halaman ini:
https://streadpay.com/docs.md(cocok untuk dibaca AI)
Mulai cepat
1. Ambil API key. Daftar, buka Dashboard → API keys → Buat key. Mulai dengan key test (str_test_...).
2. Buat tagihan QRIS.
curl -X POST https://streadpay.com/v1/payments \
-H "Authorization: Bearer str_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"amount": 10000, "merchant_ref": "ORDER-1029"}'3. Tampilkan QR ke pembeli dari qr_string atau kirim pay_url. Saat pembeli selesai bayar, server kamu menerima webhook payment.paid. Tandai pesanan lunas di sana.
Autentikasi
Kirim API key di header Authorization pada setiap request.
Authorization: Bearer str_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| Prefix | Mode | Uang sungguhan |
|---|---|---|
str_test_ |
Test. Pembayaran disimulasikan dari dashboard atau POST /v1/payments/{id}/simulate |
Tidak |
str_live_ |
Live. QRIS asli | Ya |
Simpan key hanya di server (environment variable). Jangan taruh di kode frontend, aplikasi mobile, atau repository publik.
Konsep penting
Biaya dan siapa yang menanggung
Biaya layanan per transaksi sukses: Rp100 + 0,7% dari amount. Setiap tagihan juga mendapat kode unik acak Rp1–99 agar tidak ada dua tagihan dengan nominal sama.
Atur siapa yang menanggung biaya di Dashboard → Pengaturan, atau per transaksi dengan fee_bearer.
fee_bearer |
Pembeli bayar (total_amount) |
Masuk saldo kamu (net_amount) |
|---|---|---|
customer (default) |
amount + fee + unique_code | amount |
merchant |
amount + unique_code | amount − fee |
Contoh amount 10000, kode unik 32: customer → pembeli bayar 10202, saldo +10000. merchant → pembeli bayar 10032, saldo +9830.
Status pembayaran
| Status | Arti | Final |
|---|---|---|
PENDING |
Menunggu pembeli scan dan bayar | Tidak |
PAID |
Sudah dibayar. Kirim barang / aktifkan layanan | Ya |
EXPIRED |
Lewat batas waktu, tidak dibayar | Ya |
CANCELLED |
Dibatalkan lewat API | Ya |
FAILED |
Gagal diproses penyedia pembayaran | Ya |
Anggap pembayaran lunas hanya jika status PAID dari webhook atau dari GET /v1/payments/{id}. Jangan percaya redirect atau laporan pembeli.
Mencegah tagihan dobel
Kirim header Idempotency-Key (string unik, maks 64 karakter) pada request POST. Request ulang dengan key yang sama dalam 24 jam mengembalikan respons pertama, bukan membuat tagihan baru. Cocok untuk bot yang bisa mengirim ulang saat koneksi putus.
Idempotency-Key: ORDER-1029Format respons
Semua respons memakai bentuk yang sama.
{ "success": true, "data": { } }{ "success": false, "error": { "code": "MIN_AMOUNT", "message": "Minimal transaksi Rp1.000." } }Daftar berhalaman menambahkan pagination:
{
"success": true,
"data": [ ],
"pagination": { "page": 1, "limit": 20, "total": 158, "pages": 8 }
}Endpoint
| Method | Path | Fungsi |
|---|---|---|
| POST | /v1/payments |
Buat tagihan QRIS |
| GET | /v1/payments/{id} |
Cek detail & status tagihan |
| POST | /v1/payments/{id}/cancel |
Batalkan tagihan PENDING |
| GET | /v1/transactions |
Daftar tagihan |
| GET | /v1/balance |
Saldo |
| GET | /v1/mutations |
Riwayat mutasi saldo |
| POST | /v1/withdrawals |
Ajukan pencairan |
| GET | /v1/withdrawals/{id} |
Status pencairan |
Buat pembayaran
POST /v1/payments
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
amount |
integer | Ya | Harga produk dalam rupiah. Minimal 1000 |
merchant_ref |
string | Tidak | ID pesanan di sistem kamu, maks 64 karakter. Dikirim balik di webhook |
fee_bearer |
string | Tidak | customer atau merchant. Default dari Pengaturan akun |
expires_in |
integer | Tidak | Batas bayar dalam menit, 5–1440. Default 30 |
description |
string | Tidak | Keterangan untuk pembeli, maks 255 karakter |
metadata |
object | Tidak | Data bebas (maks 2 KB), dikembalikan apa adanya di webhook |
Request:
{
"amount": 10000,
"merchant_ref": "ORDER-1029",
"description": "Topup 100 diamond",
"metadata": { "telegram_user_id": 123456789 }
}Respons 201 Created:
{
"success": true,
"data": {
"id": "pay_8fKq2LmZx01aB9cD",
"invoice_id": "INV-20261031-7KQ2XM",
"merchant_ref": "ORDER-1029",
"status": "PENDING",
"amount": 10000,
"fee": 170,
"unique_code": 32,
"total_amount": 10202,
"net_amount": 10000,
"fee_bearer": "customer",
"description": "Topup 100 diamond",
"qr_string": "00020101021226670016COM.NOBUBANK.WWW...",
"qr_image_url": "https://streadpay.com/pay/pay_8fKq2LmZx01aB9cD/qr.png",
"pay_url": "https://streadpay.com/pay/pay_8fKq2LmZx01aB9cD",
"expires_at": "2026-10-31T15:30:00Z",
"paid_at": null,
"created_at": "2026-10-31T15:00:00Z",
"metadata": { "telegram_user_id": 123456789 }
}
}Objek payment
| Field | Tipe | Keterangan |
|---|---|---|
id |
string | ID pembayaran, diawali pay_ |
invoice_id |
string | Nomor invoice StreadPay |
merchant_ref |
string | null | ID pesanan dari kamu |
status |
string | Lihat tabel status |
amount |
integer | Harga produk |
fee |
integer | Biaya layanan |
unique_code |
integer | Kode unik 1–99 |
total_amount |
integer | Nominal yang harus dibayar pembeli |
net_amount |
integer | Nominal yang masuk ke saldo saat PAID |
fee_bearer |
string | customer atau merchant |
qr_string |
string | Isi QRIS. Ubah jadi gambar QR dengan library apa pun |
qr_image_url |
string | Gambar QR (PNG) siap kirim, misalnya ke chat bot |
pay_url |
string | Halaman bayar StreadPay berisi QR dan status otomatis |
expires_at |
string | Batas waktu bayar |
paid_at |
string | null | Waktu dibayar |
created_at |
string | Waktu dibuat |
metadata |
object | null | Data dari kamu |
Cek pembayaran
GET /v1/payments/{id}
{id} boleh berupa id (pay_...) atau invoice_id. Respons: objek payment.
curl https://streadpay.com/v1/payments/pay_8fKq2LmZx01aB9cD \
-H "Authorization: Bearer str_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Pakai untuk cek ulang status jika webhook terlambat. Jangan polling lebih sering dari sekali per 5 detik.
Batalkan pembayaran
POST /v1/payments/{id}/cancel
Hanya untuk status PENDING. Respons: objek payment dengan status CANCELLED. Jika sudah PAID atau final, error INVALID_STATE.
Simulasi bayar (key test saja)
POST /v1/payments/{id}/simulate
Menandai tagihan mode test sebagai PAID dan mengirim webhook payment.paid dengan mode: "test", untuk menguji bot tanpa uang sungguhan. Dengan key live: error TEST_ONLY. Pembeli di halaman bayar tidak bisa mensimulasikan; hanya kamu dari dashboard atau API ini.
Daftar transaksi
GET /v1/transactions
| Query | Keterangan |
|---|---|
status |
Filter status, contoh PAID |
merchant_ref |
Cari berdasarkan ID pesanan kamu |
from, to |
Rentang tanggal YYYY-MM-DD (WIB) |
page |
Halaman, mulai 1 |
limit |
Jumlah per halaman, 1–100. Default 20 |
Respons: array objek payment + pagination, urut terbaru.
Saldo
GET /v1/balance
{
"success": true,
"data": { "available": 2440256, "pending": 98000, "reserved": 0 }
}| Field | Arti |
|---|---|
available |
Bisa dicairkan sekarang |
pending |
Dari pembayaran terbaru, tersedia H+1 |
reserved |
Sedang dalam proses pencairan |
Mutasi saldo
GET /v1/mutations dengan query page, limit, from, to.
{
"id": 1042,
"type": "payment_credit",
"amount": 10000,
"balance_after": 2440256,
"reference": "pay_8fKq2LmZx01aB9cD",
"description": "Pembayaran QRIS INV-20261031-7KQ2XM",
"created_at": "2026-10-31T15:02:11Z"
}type: payment_credit (pembayaran masuk), settlement (pending → tersedia), withdrawal_debit (pencairan diajukan, biaya sudah termasuk), withdrawal_refund (pencairan ditolak, saldo kembali), adjustment (koreksi oleh admin). amount negatif untuk uang keluar.
Ajukan pencairan
POST /v1/withdrawals
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
amount |
integer | Ya | Minimal 10000, tidak melebihi available |
bank_account_id |
string | Tidak | Default rekening utama di dashboard |
Biaya pencairan Rp2.500 dipotong dari nominal: cair 100000 → diterima rekening 97500.
{
"success": true,
"data": {
"id": "wd_Qm81ZpXc0LkT4rAa",
"status": "REQUESTED",
"amount": 100000,
"fee": 2500,
"net": 97500,
"bank": { "bank_code": "BCA", "account_no": "******7890", "account_name": "Budi Santoso" },
"created_at": "2026-10-31T15:10:00Z"
}
}Status pencairan: REQUESTED → PROCESSING → SUCCESS, atau REJECTED / FAILED (saldo dikembalikan utuh).
Status pencairan
GET /v1/withdrawals/{id}. Respons: objek pencairan seperti di atas.
Webhook
StreadPay mengirim POST JSON ke URL yang kamu daftarkan di Dashboard → Webhooks. Kamu bisa mendaftarkan beberapa URL; masing-masing punya signing secret sendiri (whsec_...) dan pilihan event sendiri.
Event
| Event | Kapan |
|---|---|
payment.paid |
Pembayaran lunas. Event terpenting |
payment.expired |
Tagihan kedaluwarsa |
payment.cancelled |
Tagihan dibatalkan |
settlement.available |
Dana pindah dari pending ke available |
withdrawal.success |
Pencairan berhasil ditransfer |
withdrawal.failed |
Pencairan gagal atau ditolak, saldo dikembalikan |
Header
| Header | Isi |
|---|---|
X-StreadPay-Event |
Nama event, contoh payment.paid |
X-StreadPay-Id |
ID event unik, diawali evt_. Sama di setiap percobaan ulang |
X-StreadPay-Timestamp |
Unix timestamp (detik) saat dikirim |
X-StreadPay-Signature |
sha256= + HMAC-SHA256 hex |
Payload
{
"id": "evt_Zt4kP0mQ2wX8nB1c",
"event": "payment.paid",
"mode": "live",
"created_at": "2026-10-31T15:02:11Z",
"data": {
"id": "pay_8fKq2LmZx01aB9cD",
"invoice_id": "INV-20261031-7KQ2XM",
"merchant_ref": "ORDER-1029",
"status": "PAID",
"amount": 10000,
"total_amount": 10202,
"net_amount": 10000,
"paid_at": "2026-10-31T15:02:09Z",
"metadata": { "telegram_user_id": 123456789 }
}
}data pada event payment.* adalah objek payment lengkap. mode berisi test atau live; abaikan event test di server produksi.
Tombol Kirim tes di Dashboard → Webhooks mengirim contoh payment.paid dengan "mode": "test" dan "data.test": true.
Verifikasi signature
Wajib, agar orang lain tidak bisa memalsukan notifikasi lunas.
- Ambil raw body persis seperti diterima (jangan parse lalu stringify ulang).
- Hitung
HMAC_SHA256(secret, timestamp + "." + rawBody)dalam hex. - Bandingkan dengan nilai setelah
sha256=memakai perbandingan constant-time. - Tolak jika selisih timestamp dengan jam server lebih dari 5 menit.
Aturan menerima webhook
- Balas HTTP
2xxdalam 10 detik. Proses berat (kirim pesan, update stok) lakukan setelah membalas atau di antrean. - Gagal atau timeout dikirim ulang: 1 menit, 5 menit, 30 menit, 2 jam, 6 jam (maks 5 kali).
- Webhook bisa datang lebih dari sekali. Simpan
X-StreadPay-Iddan abaikan yang sudah diproses. - Cocokkan pesanan memakai
merchant_refdan pastikanamountsama dengan pesananmu. - Di server produksi, abaikan event dengan
mode: "test". Kirim produk hanya untukpayment.paiddenganmode: "live".
Contoh integrasi
Semua contoh memakai environment variable STREADPAY_API_KEY dan STREADPAY_WEBHOOK_SECRET.
Node.js
// npm install express
const express = require('express');
const crypto = require('crypto');
const API = 'https://streadpay.com/v1';
const KEY = process.env.STREADPAY_API_KEY;
const SECRET = process.env.STREADPAY_WEBHOOK_SECRET;
// Buat tagihan. Panggil saat pembeli checkout / mengetik perintah beli di bot.
async function createPayment(orderId, amount, metadata = {}) {
const res = await fetch(`${API}/payments`, {
method: 'POST',
headers: {
Authorization: `Bearer ${KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': orderId,
},
body: JSON.stringify({ amount, merchant_ref: orderId, metadata }),
});
const json = await res.json();
if (!json.success) throw new Error(`${json.error.code}: ${json.error.message}`);
return json.data; // kirim data.qr_image_url atau data.pay_url ke pembeli
}
const app = express();
// Webhook: pakai express.raw agar signature dihitung dari body asli.
app.post('/webhook/streadpay', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-StreadPay-Timestamp');
const signature = (req.get('X-StreadPay-Signature') || '').replace('sha256=', '');
const expected = crypto.createHmac('sha256', SECRET).update(`${timestamp}.${req.body}`).digest('hex');
const valid = signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
if (!valid || !fresh) return res.status(401).end();
res.sendStatus(200); // balas dulu, proses setelahnya
const event = JSON.parse(req.body);
if (event.event === 'payment.paid') {
// TODO: cek event.id belum pernah diproses, lalu tandai pesanan lunas
console.log('Lunas:', event.data.merchant_ref, event.data.amount);
}
});
app.listen(3000);PHP
<?php
// Buat tagihan
function createPayment(string $orderId, int $amount): array {
$ch = curl_init('https://streadpay.com/v1/payments');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('STREADPAY_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . $orderId,
],
CURLOPT_POSTFIELDS => json_encode(['amount' => $amount, 'merchant_ref' => $orderId]),
]);
$json = json_decode(curl_exec($ch), true);
curl_close($ch);
if (!$json['success']) throw new Exception($json['error']['code']);
return $json['data'];
}
// webhook.php
$raw = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_STREADPAY_TIMESTAMP'] ?? '';
$signature = str_replace('sha256=', '', $_SERVER['HTTP_X_STREADPAY_SIGNATURE'] ?? '');
$expected = hash_hmac('sha256', $timestamp . '.' . $raw, getenv('STREADPAY_WEBHOOK_SECRET'));
if (!hash_equals($expected, $signature) || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit;
}
http_response_code(200);
$event = json_decode($raw, true);
if ($event['event'] === 'payment.paid') {
// tandai pesanan $event['data']['merchant_ref'] lunas
}Python
# pip install requests flask
import hashlib, hmac, os, time
import requests
from flask import Flask, request, abort
API = "https://streadpay.com/v1"
KEY = os.environ["STREADPAY_API_KEY"]
SECRET = os.environ["STREADPAY_WEBHOOK_SECRET"]
def create_payment(order_id: str, amount: int) -> dict:
r = requests.post(
f"{API}/payments",
headers={"Authorization": f"Bearer {KEY}", "Idempotency-Key": order_id},
json={"amount": amount, "merchant_ref": order_id},
timeout=15,
)
body = r.json()
if not body["success"]:
raise RuntimeError(body["error"]["code"])
return body["data"] # kirim body["data"]["qr_image_url"] ke pembeli
app = Flask(__name__)
@app.post("/webhook/streadpay")
def webhook():
raw = request.get_data()
ts = request.headers.get("X-StreadPay-Timestamp", "")
sig = request.headers.get("X-StreadPay-Signature", "").replace("sha256=", "")
expected = hmac.new(SECRET.encode(), f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected) or abs(time.time() - int(ts or 0)) > 300:
abort(401)
event = request.get_json()
if event["event"] == "payment.paid":
pass # tandai pesanan event["data"]["merchant_ref"] lunas
return "", 200Alur untuk bot (Telegram, WhatsApp, Discord)
- Pembeli memilih produk → bot memanggil
POST /v1/paymentsdenganmerchant_ref= ID pesanan danmetadataberisi ID chat pembeli. - Bot mengirim gambar dari
qr_image_urldan teks "Bayar Rptotal_amountsebelumexpires_at". - Webhook
payment.paidmasuk → ambil ID chat daridata.metadata→ kirim produk / pesan sukses. - Webhook
payment.expired→ kirim pesan "Tagihan kedaluwarsa, ketik /beli untuk membuat ulang".
Kode error
| HTTP | code |
Arti dan solusi |
|---|---|---|
| 401 | INVALID_API_KEY |
Key salah, dihapus, atau tidak dikirim |
| 403 | ACCOUNT_INACTIVE |
Akun belum verifikasi email atau ditangguhkan |
| 403 | KYC_REQUIRED |
Mode live butuh verifikasi identitas di dashboard |
| 404 | NOT_FOUND |
ID tidak ditemukan atau bukan milik akunmu |
| 409 | DUPLICATE_REQUEST |
Idempotency-Key sama dipakai untuk isi request berbeda |
| 409 | INVALID_STATE |
Aksi tidak bisa untuk status saat ini, contoh batalkan tagihan PAID |
| 409 | IN_PROGRESS |
Request dengan Idempotency-Key yang sama masih diproses. Ulangi beberapa detik lagi |
| 403 | TEST_ONLY |
Endpoint simulasi hanya untuk key test |
| 422 | VALIDATION_ERROR |
Field salah format. Lihat error.message |
| 422 | MIN_AMOUNT |
amount kurang dari 1000 |
| 422 | INSUFFICIENT_BALANCE |
Saldo available tidak cukup untuk pencairan |
| 429 | RATE_LIMITED |
Terlalu banyak request. Tunggu sesuai header Retry-After |
| 502 | PROVIDER_ERROR |
Penyedia QRIS sedang gangguan. Coba lagi beberapa saat |
| 500 | SERVER_ERROR |
Kesalahan di StreadPay. Coba lagi; hubungi kami jika berulang |
Batas request
60 request per menit per API key. Header respons RateLimit-Remaining menunjukkan sisa kuota.
Untuk AI assistant
Jika kamu AI yang membantu developer mengintegrasikan StreadPay, ikuti aturan ini:
- Panggil API hanya dari server. API key di environment variable
STREADPAY_API_KEY, jangan di-hardcode. - Buat tagihan dengan
POST /v1/payments; setmerchant_ref= ID pesanan danIdempotency-Key= ID pesanan. - Tampilkan
total_amount(bukanamount) sebagai nominal yang dibayar pembeli. - Tandai pesanan lunas hanya dari webhook
payment.paidyang signature-nya valid, atau dariGET /v1/payments/{id}dengan statusPAID. - Verifikasi signature dari raw body; simpan
X-StreadPay-Iduntuk mencegah proses ganda. - Uji dulu dengan key
str_test_, lalu ganti kestr_live_.