Dokumentasi API

Integrasi pakai AI

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
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.

HTTP
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.

HTTP
Idempotency-Key: ORDER-1029

Format respons

Semua respons memakai bentuk yang sama.

JSON
{ "success": true, "data": { } }
JSON
{ "success": false, "error": { "code": "MIN_AMOUNT", "message": "Minimal transaksi Rp1.000." } }

Daftar berhalaman menambahkan pagination:

JSON
{
  "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:

JSON
{
  "amount": 10000,
  "merchant_ref": "ORDER-1029",
  "description": "Topup 100 diamond",
  "metadata": { "telegram_user_id": 123456789 }
}

Respons 201 Created:

JSON
{
  "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
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

JSON
{
  "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.

JSON
{
  "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.

JSON
{
  "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 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

JSON
{
  "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.

  1. Ambil raw body persis seperti diterima (jangan parse lalu stringify ulang).
  2. Hitung HMAC_SHA256(secret, timestamp + "." + rawBody) dalam hex.
  3. Bandingkan dengan nilai setelah sha256= memakai perbandingan constant-time.
  4. Tolak jika selisih timestamp dengan jam server lebih dari 5 menit.

Aturan menerima webhook

  • Balas HTTP 2xx dalam 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-Id dan abaikan yang sudah diproses.
  • Cocokkan pesanan memakai merchant_ref dan pastikan amount sama dengan pesananmu.
  • Di server produksi, abaikan event dengan mode: "test". Kirim produk hanya untuk payment.paid dengan mode: "live".

Contoh integrasi

Semua contoh memakai environment variable STREADPAY_API_KEY dan STREADPAY_WEBHOOK_SECRET.

Node.js

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
<?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

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 "", 200

Alur untuk bot (Telegram, WhatsApp, Discord)

  1. Pembeli memilih produk → bot memanggil POST /v1/payments dengan merchant_ref = ID pesanan dan metadata berisi ID chat pembeli.
  2. Bot mengirim gambar dari qr_image_url dan teks "Bayar Rptotal_amount sebelum expires_at".
  3. Webhook payment.paid masuk → ambil ID chat dari data.metadata → kirim produk / pesan sukses.
  4. 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; set merchant_ref = ID pesanan dan Idempotency-Key = ID pesanan.
  • Tampilkan total_amount (bukan amount) sebagai nominal yang dibayar pembeli.
  • Tandai pesanan lunas hanya dari webhook payment.paid yang signature-nya valid, atau dari GET /v1/payments/{id} dengan status PAID.
  • Verifikasi signature dari raw body; simpan X-StreadPay-Id untuk mencegah proses ganda.
  • Uji dulu dengan key str_test_, lalu ganti ke str_live_.