Dokumentasi

API QRIS & Nomor OTP, webhooks, dan panduan. Base URL API: https://skirkdonate.my.id/api/v1

Daftar isi
Mulai
Mulai cepatAutentikasi & batasan
API QRIS
API QRIS: terima pembayaran
API Nomor OTP
API Nomor OTP: pesan & ambil daftar
Webhooks
Webhooks: pembayaran & OTP
Referensi
Kode error
Panduan
Nomor OTP lewat halaman webNotifikasi: lonceng, popup, dan suara
Unduh & salin
⬇ Semua (.md) ⬇ Nomor OTP (.md)

Mulai cepat

Skirk punya satu API untuk QRIS (terima pembayaran) dan Nomor OTP (nomor virtual untuk SMS verifikasi), ditambah webhook yang memberi tahu server kamu saat ada pembayaran atau kode OTP masuk.

  1. Buka menu API, buat key, dan isi IP server kamu (wajib). Key asli hanya ditampilkan sekali, jadi simpan.
  2. Kirim request ke https://skirkdonate.my.id/api/v1/... dengan header Authorization: Bearer <key>.
  3. (Opsional) isi Callback URL pada key untuk menerima webhook.
Contoh pertama: buat QRIS Rp25.000
cURL
curl -X POST https://skirkdonate.my.id/api/v1/qris \
  -H "Authorization: Bearer $SKIRK_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": 25000, "order_id": "INV-1001", "name": "Budi", "message": "Terima kasih"}'

Autentikasi & batasan

AturanKeterangan
AutentikasiHeader Authorization: Bearer sk_live_... (atau X-API-Key). Key di URL tidak didukung. Simpan key hanya di server.
HTTPSWajib. Request non-HTTPS ditolak (400).
Allowlist IPHanya IP yang didaftarkan pada key yang diterima (IPv4, IPv6, atau CIDR). Selain itu dijawab 403.
Rate limitDefault 30 request/menit per key. Header X-RateLimit-Limit dan X-RateLimit-Remaining ada di tiap respons. Melewati batas dijawab 429 + Retry-After. Butuh lebih? Minta admin menaikkannya.
Percobaan gagalIP yang gagal (401/403) 20 kali dalam 10 menit diblokir 10 menit.
Batas invoiceMaksimal 50 invoice pending dan 1000 invoice per 24 jam per key. Invoice berlaku 30 menit.

Semua respons berbentuk JSON: {"status":"success","data":...} atau {"status":"error","message":"..."}. Daftar kode error ada di Kode error.

API QRIS: terima pembayaran

Buat QRIS

POST /api/v1/qris membuat invoice QRIS dinamis. Nominal bayar bisa bertambah kode unik (1-99) supaya pembayaran bisa dicocokkan. Contoh request ada di Mulai cepat.

FieldTipeKeterangan
amountintegerWajib. Nominal dalam Rupiah (default Rp1.000 - Rp10.000.000).
order_idstringOpsional, 1-64 karakter (huruf, angka, _ . : -). Idempoten: order_id sama + nominal sama mengembalikan invoice yang sama; nominal beda dijawab 409.
namestringOpsional, nama pembayar (maks. 40 karakter).
messagestringOpsional, pesan (maks. 200 karakter).
Respons 201
{
  "status": "success",
  "data": {
    "code": "9f8e7d6c5b4a3921",
    "order_id": "INV-1001",
    "amount": 25107,
    "base_amount": 25000,
    "fee": 275,
    "fee_payer": "creator",
    "net": 24832,
    "status": "pending",
    "created_at": "2026-10-09T10:00:00+07:00",
    "expires_at": "2026-10-09T10:30:00+07:00",
    "paid_at": null,
    "pay_url": "https://skirkdonate.my.id/pay/9f8e7d6c5b4a3921",
    "qris": "00020101021226..."
  }
}
Field responsArti
amountTotal yang harus dibayar pembeli (termasuk kode unik, dan biaya admin bila ditanggung pembeli).
base_amountNominal yang kamu minta.
fee / fee_payerBiaya admin dan penanggungnya: creator atau donor.
netYang masuk ke saldo kamu.
statuspending, paid, atau expired.
pay_url / qrisHalaman bayar siap pakai, dan teks QRIS dinamis (hanya saat pending) bila kamu ingin membuat gambar QR sendiri.

Cek status

GET /api/v1/qris/{code} mengembalikan bentuk data yang sama. Hanya invoice milik key itu yang bisa dibaca. Untuk invoice penting, gunakan ini sebagai cadangan webhook.

Cek saldo

GET /api/v1/balance mengembalikan saldo akunmu.

Respons
{
  "status": "success",
  "data": {
    "available": 150000,
    "pending_withdrawal": 0,
    "withdrawn": 500000,
    "total_received": 650000
  }
}

API Nomor OTP: pesan & ambil daftar

Pesan nomor sekali pakai untuk menerima SMS verifikasi (WhatsApp, Telegram, dll.) dan ambil daftar negara, layanan, operator, serta semua pilihan harga (termurah dulu). Memakai API key dan saldo yang sama dengan QRIS.

Alur singkat

  1. Ambil country_id dan service_id dari /otp/countries dan /otp/services.
  2. GET /otp/products untuk melihat semua harga, lalu pilih satu baris (yang termurah ada di atas).
  3. POST /otp/orders dengan data baris itu, price-nya, dan ref milikmu.
  4. Tunggu kode: terima webhook otp.received, atau ulangi GET /otp/orders/{id} tiap 3-5 detik.
  5. Panggil finish setelah kode dipakai, atau cancel bila tidak ada SMS (saldo kembali).

Endpoint

EndpointFungsi
GET /otp/countriesDaftar negara (id, name, emoji, dial_code).
GET /otp/services?country_id=Daftar layanan/aplikasi untuk negara itu.
GET /otp/operators?country_id=&service_id=Operator yang punya stok (operator_id null = acak/any).
GET /otp/products?country_id=&service_id=Semua opsi harga, termurah ke termahal, beserta stok. Opsional: operator_id=.
POST /otp/ordersPesan satu nomor di harga yang dipilih.
GET /otp/ordersDaftar pesanan akunmu, terbaru dulu. Opsional: status=, limit= (maks. 100), offset=.
GET /otp/orders/{id}Status satu pesanan; kode OTP muncul di sini.
POST /otp/orders/{id}/cancelBatalkan sebelum ada SMS; saldo kembali.
POST /otp/orders/{id}/finishTandai selesai setelah kode dipakai.
Respons GET /otp/products: semua harga, termurah dulu
{
  "status": "success",
  "data": [
    {"catalog_product_id": 88, "operator_id": 42,   "operator": "Telkomsel",  "available": 142, "price": 2945},
    {"catalog_product_id": 88, "operator_id": null, "operator": "Acak (Any)", "available": 30,  "price": 3300},
    {"catalog_product_id": 88, "operator_id": 43,   "operator": "Indosat",    "available": 12,  "price": 3600}
  ]
}

Memesan nomor

FieldTipeKeterangan
country_idintegerWajib. Dari /otp/countries.
service_idintegerWajib. Dari /otp/services.
catalog_product_idintegerWajib. Dari baris yang dipilih di /otp/products.
operator_idinteger / nullDari baris yang sama (null untuk acak/any).
priceintegerWajib. Harga baris itu (Rupiah). Harus sama persis dengan harga di daftar.
refstringOpsional tetapi disarankan, 1-64 karakter (huruf, angka, _ . : -). Ref sama = pesanan sama, jadi request yang timeout aman diulang tanpa memesan dua kali.
Request POST /otp/orders
{
  "country_id": 7,
  "service_id": 1,
  "catalog_product_id": 88,
  "operator_id": 42,
  "price": 2945,
  "ref": "ORD-1001"
}
Harga dicek ulang di server saat memesan. Bila harga atau stok berubah, pesanan ditolak dengan price_changed: ambil /otp/products lagi lalu pilih ulang. Saldo ditahan sebesar price, dan kembali otomatis bila pesanan batal, kedaluwarsa, atau gagal.

Objek pesanan

Respons 201
{
  "status": "success",
  "data": {
    "id": 90210,
    "ref": "ORD-1001",
    "status": "waiting_sms",
    "phone": "+6281234567890",
    "service": "WhatsApp",
    "country": "Indonesia",
    "operator": "Telkomsel",
    "price": 2945,
    "otp_code": null,
    "otp_message": null,
    "sms_revision": 0,
    "created_at": "2026-10-09T10:00:00+07:00",
    "expires_at": "2026-10-09T10:20:00+07:00",
    "expires_in": 1200,
    "can_cancel": true,
    "can_finish": false
  }
}
FieldArti
id / refID pesanan (dipakai di endpoint lain) dan referensi yang kamu kirim.
statusLihat tabel status di bawah.
phoneNomor yang dipesan; masukkan di aplikasi tujuan.
otp_code / otp_messageKode terdeteksi dan teks SMS lengkap. Kode bisa null sementara otp_message terisi (mis. SMS berisi tautan).
sms_revisionNaik setiap ada SMS baru. Abaikan data yang sms_revision-nya lebih kecil dari yang sudah kamu simpan.
priceHarga yang ditagihkan (Rupiah).
expires_at / expires_inBatas waktu sewa (ISO 8601) dan sisa detiknya.
can_cancel / can_finishAksi yang diizinkan saat ini.

Status pesanan

statusArtiLangkah berikutnya
creatingPesanan sedang dibuat.Cek lagi sebentar.
waiting_smsNomor siap, menunggu SMS.Kirim kode ke nomor itu. Bisa dibatalkan setelah jeda singkat (biasanya sekitar 2 menit).
otp_receivedSMS masuk.Ambil otp_code / otp_message, lalu panggil finish. Tidak bisa dibatalkan lagi.
completedSelesai.Saldo terpotong.
canceled, expired, failedDibatalkan, kedaluwarsa tanpa SMS, atau nomor tidak terbentuk.Saldo kembali (atau tidak pernah terpotong).
pending_adminHasil pemesanan belum pasti (mis. koneksi terputus). Dibalas 202.Saldo ditahan. Cek GET /otp/orders/{id}; bila lama, hubungi admin dengan menyebut id.
Contoh alur lengkap: cari harga termurah, pesan, tunggu kode, selesai
cURL
# 1) Lihat semua harga (termurah di atas). country_id & service_id dari /otp/countries dan /otp/services
curl "https://skirkdonate.my.id/api/v1/otp/products?country_id=7&service_id=1" -H "Authorization: Bearer $SKIRK_KEY"

# 2) Pesan nomor di harga yang dipilih (price harus sama persis dengan hasil langkah 1)
curl -X POST https://skirkdonate.my.id/api/v1/otp/orders -H "Authorization: Bearer $SKIRK_KEY" -H "Content-Type: application/json" \
  -d '{"country_id":7,"service_id":1,"catalog_product_id":88,"operator_id":42,"price":2945,"ref":"ORD-1001"}'

# 3) Cek pesanan sampai status otp_received (ulangi tiap 3-5 detik, atau pakai webhook)
curl https://skirkdonate.my.id/api/v1/otp/orders/90210 -H "Authorization: Bearer $SKIRK_KEY"

# 4) Setelah kode dipakai: selesai. Bila tidak ada SMS: batalkan (saldo kembali)
curl -X POST https://skirkdonate.my.id/api/v1/otp/orders/90210/finish -H "Authorization: Bearer $SKIRK_KEY"
curl -X POST https://skirkdonate.my.id/api/v1/otp/orders/90210/cancel -H "Authorization: Bearer $SKIRK_KEY"
Cara paling hemat menunggu kode adalah webhook otp.received. Polling cukup sebagai cadangan; tetap patuhi rate limit. Pesanan lewat API juga muncul di halaman Nomor OTP dengan saldo yang sama.

Webhooks: pembayaran & OTP

Isi Callback URL pada API key (menu API). Skirk lalu mengirim POST JSON ke URL itu saat pembayaran lunas atau saat pesanan Nomor OTP-mu berubah. Secret penandatangan (whsec_...) ada di kartu key yang sama dan bisa diganti kapan saja.

Satu URL menerima semua event. Cabangkan berdasarkan field event (atau header X-Skirk-Event). Callback OTP hanya dikirim untuk pesanan yang dibuat lewat API key itu.

Event

eventKapan dikirim
payment.paidInvoice yang dibuat lewat key itu lunas.
otp.receivedSMS masuk. Dikirim lagi untuk setiap SMS baru (sms_revision naik); otp_code bisa null sementara otp_message terisi.
otp.expiredMasa sewa habis tanpa SMS. Saldo sudah dikembalikan.
otp.canceledPesanan berakhir dibatalkan dari sisi sumber nomor. Saldo sudah dikembalikan.

Header & payload

HeaderIsi
X-Skirk-EventNama event, mis. payment.paid.
X-Skirk-TimestampWaktu kirim (detik, Unix).
X-Skirk-Signaturesha256=<hex>: HMAC-SHA256 dari "<timestamp>.<body mentah>" dengan secret key-mu.
Contoh payload
payment.paid
{
  "event": "payment.paid",
  "code": "9f8e7d6c5b4a3921",
  "order_id": "INV-1001",
  "amount": 25107,
  "base_amount": 25000,
  "fee": 275,
  "fee_payer": "creator",
  "net": 24832,
  "status": "paid",
  "donor_name": "Budi",
  "message": "Terima kasih",
  "paid_at": "2026-10-09T10:05:00+07:00"
}

Isi otp.* sama dengan objek pesanan ditambah event; expires_in adalah snapshot saat event terjadi. Untuk status paling baru, panggil GET /otp/orders/{id}.

Verifikasi tanda tangan

  1. Ambil body mentah (jangan di-decode/di-format ulang dulu).
  2. Hitung HMAC-SHA256(timestamp + "." + body, secret) dan bandingkan dengan header X-Skirk-Signature secara constant-time.
  3. Tolak bila timestamp lebih dari 5 menit dari waktu sekarang.
  4. Balas status 2xx secepatnya; proses berat jalankan di belakang.
Contoh penerima webhook
PHP
<?php
$secret = 'CALLBACK_SECRET_KAMU';                      // whsec_... dari menu API
$body   = file_get_contents('php://input');            // body MENTAH, jangan di-decode dulu
$ts     = $_SERVER['HTTP_X_SKIRK_TIMESTAMP'] ?? '';
$sig    = $_SERVER['HTTP_X_SKIRK_SIGNATURE'] ?? '';
$calc   = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $secret);

if (!ctype_digit($ts) || abs(time() - (int)$ts) > 300 || !hash_equals($calc, $sig)) {
    http_response_code(401); exit;
}
$data = json_decode($body, true);
switch ($data['event']) {
    case 'payment.paid':  /* tandai order $data['order_id'] lunas */ break;
    case 'otp.received':  /* simpan $data['otp_code'] / $data['otp_message'] untuk pesanan $data['id'] */ break;
    case 'otp.expired':
    case 'otp.canceled':  /* pesanan berakhir tanpa kode, saldo sudah kembali */ break;
}
http_response_code(200); echo 'ok';                     // 2xx = diterima

Pengiriman ulang

Bila balasanmu bukan 2xx (atau koneksi gagal), Skirk mengirim ulang sampai 6 kali percobaan: segera, lalu setelah 1 menit, 5 menit, 30 menit, 2 jam, dan 6 jam. Setelah itu dianggap gagal. Karena itu, event yang sama bisa tiba lebih dari sekali atau tidak berurutan: jadikan pemrosesan idempoten (pakai order_id atau code untuk pembayaran, id + sms_revision untuk OTP).

URL harus HTTPS, memakai port 80, 443, 8080, atau 8443, dan tidak boleh mengarah ke alamat IP privat/internal.

Uji penerimamu dari terminal (hitung tanda tangan dengan openssl)
SECRET="whsec_xxxxxxxx"; TS=$(date +%s)
BODY='{"event":"payment.paid","code":"test","order_id":"INV-TEST","amount":25107,"status":"paid"}'
SIG="sha256=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $NF}')"
curl -X POST https://server-kamu.com/skirk-callback \
  -H "Content-Type: application/json" \
  -H "X-Skirk-Event: payment.paid" -H "X-Skirk-Timestamp: $TS" -H "X-Skirk-Signature: $SIG" \
  -d "$BODY"

Kode error

Error berbentuk {"status":"error","message":"..."}. Pada API Nomor OTP ada tambahan code yang stabil untuk percabangan logika; pesan message bisa berubah.

HTTPcodePenyebab
400-Bukan HTTPS, atau body bukan JSON.
401-API key tidak valid.
403-Key dicabut / pemilik diblokir, atau IP tidak ada di allowlist (respons memuat your_ip).
404-Endpoint, invoice, atau pesanan tidak ditemukan.
409-QRIS: order_id sudah dipakai dengan nominal berbeda. OTP: cancel/finish ditolak (belum bisa dibatalkan, SMS sudah masuk, dst.); respons memuat order terbaru.
409price_changedOTP: harga atau stok berubah. Ambil /otp/products lagi lalu pilih ulang.
409no_numberOTP: nomor tidak tersedia saat ini; saldo tidak terpotong.
409ref_conflictOTP: ref sudah dipakai untuk produk lain. Pakai ref baru.
402insufficient_balanceOTP: saldo tersedia kurang dari price.
422-Parameter tidak valid (nominal di luar batas, order_id/ref salah format, dst.), atau pesanan OTP gagal diproses (saldo tidak terpotong).
424source_unavailableOTP: sumber nomor sedang tidak merespons; coba lagi sebentar.
429-Rate limit, terlalu banyak invoice pending, atau batas harian tercapai. Hormati header Retry-After.
429max_activeOTP: batas pesanan berjalan per akun tercapai.
503-Layanan Nomor OTP belum diaktifkan admin.

Nomor OTP lewat halaman web

Menu Nomor OTP menjual nomor sekali pakai untuk menerima SMS verifikasi dengan saldo Skirk-mu. Menu hanya muncul bila admin sudah mengaktifkannya.

  1. Pilih negara, lalu layanan/aplikasi (ada kolom cari), lalu operator (atau biarkan "Acak").
  2. Semua harga tampil dari yang termurah, lengkap dengan operator dan stok. Klik Beli pada harga yang kamu mau, lalu konfirmasi.
  3. Masukkan nomor yang tampil di aplikasi tujuan dan minta kode verifikasi.
  4. Kode OTP muncul otomatis di kartu pesanan dan bisa disalin dengan tombol Salin.
  5. Setelah kode dipakai, klik Selesai. Bila tidak ada SMS, klik Batalkan & kembalikan saldo.
Harga yang tampil sudah final dan dicek ulang saat kamu menekan Beli. Bila harga atau stok berubah, pembelian ditolak dan kamu memilih ulang, jadi tidak pernah ditagih lebih mahal dari yang kamu lihat. Saldo ditahan saat memesan dan kembali otomatis bila pesanan batal, kedaluwarsa, atau gagal; saldo benar-benar terpakai hanya bila statusnya Selesai.

Arti status

StatusArtiYang bisa kamu lakukan
Menunggu SMSNomor siap, menunggu SMS masuk.Kirim kode ke nomor itu. Tombol Batalkan aktif setelah jeda singkat (sekitar 2 menit) dan selama belum ada SMS.
Kode masukSMS sudah diterima.Salin kodenya, pakai di aplikasi, lalu klik Selesai.
SelesaiPesanan tuntas.Saldo tetap terpotong.
Dibatalkan / Kedaluwarsa / GagalTidak ada SMS masuk atau nomor tidak terbentuk.Saldo kembali otomatis. Pesan ulang bila perlu.
Menunggu adminHasil pemesanan belum pasti (mis. koneksi terputus).Saldo ditahan, bukan hilang. Admin akan memutuskan; hubungi admin bila lebih dari beberapa menit.

Masalah umum

MasalahSolusi
Kode tidak kunjung masukPastikan nomor dimasukkan lengkap dengan kode negara dan kode sudah diminta di aplikasi tujuan. Bila tetap tidak ada, batalkan (saldo kembali) dan coba operator atau harga lain.
Tombol Batalkan tidak aktifAda jeda minimum setelah memesan, atau SMS sudah masuk (pesanan tidak bisa dibatalkan lagi).
"Saldo tidak cukup"Saldo tersedia = saldo dikurangi pesanan yang masih berjalan. Isi saldo atau selesaikan/batalkan pesanan lain.
"Maksimal ... pesanan berjalan"Selesaikan atau batalkan pesanan yang sedang berjalan dulu.
"Harga/stok sudah berubah" atau daftar kosongDaftar dimuat ulang otomatis; pilih lagi. Bila kosong, stok habis: coba operator "Acak" atau layanan lain.
SMS masuk tetapi kode kosongBeberapa SMS berisi tautan, bukan angka. Teks SMS lengkap tetap tampil di kartu pesanan.

Nomor bersifat sekali pakai untuk satu verifikasi; pesan lagi untuk verifikasi berikutnya. Gunakan hanya untuk akun dan keperluan yang sah.

Notifikasi: lonceng, popup, dan suara

Setelah masuk, ikon lonceng di bar atas memberi tahu kejadian penting tanpa perlu membuka halaman tertentu. Angka merah di lonceng = jumlah notifikasi yang belum dibaca.

Kamu menerima notifikasi saatContoh
Pembayaran QRIS lunas"Pembayaran masuk Rp24.832 · dari Budi · INV-1001"
Kode OTP masuk"Kode OTP masuk: 123456 · WhatsApp +6281..."
Pesanan OTP kedaluwarsa / dibatalkanSaldo sudah dikembalikan.
Penarikan saldo diproses adminSudah ditransfer, atau ditolak dan saldo kembali.
(Admin) permintaan tarik saldo baru, pesanan OTP butuh keputusan, backup Telegram gagalLangsung membuka tab yang relevan.

Cara kerja

  1. Popup mengambang muncul di pojok layar saat ada kejadian baru (maks. 3 sekaligus, hilang otomatis). Klik popup untuk langsung ke halamannya.
  2. Suara: nada pendek berbeda untuk pembayaran, OTP, pengembalian saldo, dan penarikan. Getar singkat juga dicoba di HP. Atur lewat tombol Suara: nyala/mati di panel lonceng; pilihan ini tersimpan per perangkat.
  3. Panel lonceng menampilkan 20 notifikasi terakhir. Menutup panel menandai semuanya dibaca, atau klik Tandai dibaca.
  4. Judul tab ikut berubah menjadi (2) Nomor OTP selama ada notifikasi belum dibaca.
Browser menahan suara sampai kamu mengetuk atau menekan tombol sekali di halaman itu. Bila suara belum aktif, panel menampilkan petunjuk "Ketuk layar sekali". Notifikasi hanya tampil selama situs terbuka di tab browser (belum berupa notifikasi sistem saat browser ditutup). Untuk menerima kabar di sisi server kamu, pakai webhook.
💬 CS