Dokumentasi Developer — Sarana Payment API

Integrasi server-to-server ke payment gateway Sarana memakai API key. Surface developer berada di /api/v2 (Client / Developer API).

Tersedia dua environment — sandbox untuk development/uji coba (tanpa uang nyata), production untuk transaksi nyata:

EnvironmentBase URLCatatan
Productionhttps://api.internal.saranatechnology.com/api/v2Transaksi & payout uang nyata
Sandboxhttps://api.internal-go.saranatechnology.com/api/v2Payment provider mode test; data & API key terpisah dari production

Swagger adalah sumber kebenaran untuk request/response tiap endpoint. Halaman ini panduan getting started yang merangkumnya. Path & payload identik di kedua environment — cukup ganti host (dan API key) saat pindah sandbox → production. Mulai dari sandbox, pindah ke production setelah alur end-to-end teruji.

1. Mendapatkan API key

Satu client = satu API key aktif. Generate ulang akan mengganti key lama (key lama langsung non-aktif). Dua cara memperolehnya:

JalurSiapaSyarat
Portal klien (self-service)Client sendiri di client.saranatechnology.com → menu API KeysAkun harus punya akses developer (di-approve admin)
Admin panelAdmin Sarana menerbitkan untuk clientTidak ada — admin bisa untuk client mana pun

Akses developer adalah izin sisi client: hanya client dengan akses developer yang boleh men-generate key-nya sendiri lewat portal. Key yang sudah terbit tetap valid dipakai tanpa bergantung pada flag tersebut.

Format key: sk_live_ + 48 karakter heksadesimal. Simpan baik-baik — key penuh hanya ditampilkan sekali saat dibuat.

Key per-client, bukan per-app. Satu client bisa punya banyak apps, tapi API key dimiliki di level client — satu key dipakai untuk semua apps client tersebut. Anda tidak perlu generate key per-app. client_id juga otomatis diambil dari key, jadi tidak perlu dikirim di body request. Saat client punya >1 app, sertakan app_id pada request pembayaran agar biaya transaksi (transaction_fee) yang benar dipakai — lihat bagian Apps.

2. Autentikasi

Sertakan API key pada header X-API-Key di setiap request:

X-API-Key: sk_live_<48-karakter-hex-key-anda>

Tanpa key yang valid & aktif → 401 Unauthorized.

API key khusus untuk endpoint integrasi (payments, fees, withdrawals, transactions). Endpoint portal (login, tiket, dsb.) memakai Bearer token, bukan API key.

3. Format response

Semua response memakai envelope seragam:

{
  "success": true,
  "message": "...",
  "code": 200,
  "data": { },
  "meta": { "total": 0, "per_page": 10, "current_page": 1, "last_page": 1 }
}

meta hanya muncul pada endpoint berpaginasi. Saat error, success=false dan message berisi alasannya, code = HTTP status.

CodeArti
400Request tidak valid (field wajib kosong / salah format)
401API key tidak ada / tidak valid
404Resource tidak ditemukan
500Kesalahan server

4. Quickstart — membuat pembayaran

Simpan key sebagai environment variable agar tidak ikut tercatat:

export SARANA_API_KEY="sk_live_<48-karakter-hex-key-anda>"

client_id tidak perlu dikirim — diambil dari API key. Bila client punya lebih dari satu app, sertakan app_id (lihat bagian Apps) agar transaction_fee app yang benar dipakai.

Virtual Account

curl -X POST https://api.internal.saranatechnology.com/api/v2/payments/va \
  -H "X-API-Key: $SARANA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "app_id": 45,
    "amount": 150000,
    "bank_code": "BCA",
    "customer_name": "Budi Santoso"
  }'

Contoh memakai host production. Untuk uji coba, ganti host ke sandbox api.internal-go.saranatechnology.com (dengan API key sandbox).

Response 201 berisi nomor VA yang ditampilkan ke pembeli.

QRIS

curl -X POST .../api/v2/payments/qris \
  -H "X-API-Key: $SARANA_API_KEY" -H "Content-Type: application/json" \
  -d '{ "app_id": 45, "amount": 50000 }'

Response berisi qr_string untuk dirender sebagai QR.

E-wallet

curl -X POST .../api/v2/payments/ewallet \
  -H "X-API-Key: $SARANA_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "app_id": 45, "amount": 75000,
    "channel_code": "DANA", "customer_phone": "08123456789",
    "success_url": "https://toko-anda.com/selesai"
  }'

Response berisi link / deeplink pembayaran (OVO, DANA, SHOPEEPAY, LINKAJA).

Invoice

curl -X POST .../api/v2/payments/invoice \
  -H "X-API-Key: $SARANA_API_KEY" -H "Content-Type: application/json" \
  -d '{ "app_id": 45, "amount": 250000 }'

Cek status pembayaran

curl .../api/v2/payments/{id}/status -H "X-API-Key: $SARANA_API_KEY"

Daftar metode tersedia: GET .../api/v2/payments/methods.

5. Apps

Satu client bisa punya banyak apps. Tiap app punya transaction_fee sendiri, jadi saat membuat pembayaran Anda perlu menyebut app mana lewat app_id (wajib bila app lebih dari satu). Ambil daftar app milik Anda:

curl .../api/v2/apps -H "X-API-Key: $SARANA_API_KEY"

Response (client_id otomatis dari key — tidak perlu dikirim):

{
  "success": true,
  "data": [
    {
      "id": 12,
      "app_id": 45,
      "type": "Production",
      "url": "https://toko-anda.com",
      "app": { "id": 45, "kode": "APP-XYZ", "name": "Toko XYZ", "jenis": "B2B" }
    }
  ]
}

Pakai app_id dari sini di request pembayaran. Bila client hanya punya satu app, app_id boleh dikosongkan — sistem memakai satu-satunya app itu.

6. Profil akun sendiri

Baca & perbarui profil client Anda sendiri (nama, PIC, alamat, serta norek + bank tujuan penarikan) langsung lewat API key — tanpa login portal. client_id diambil dari key, jadi tidak perlu dikirim.

# Baca profil
curl .../api/v2/account -H "X-API-Key: $SARANA_API_KEY"

Response:

{
  "success": true,
  "code": 200,
  "message": "Account retrieved",
  "data": {
    "id": 14, "nama": "Toko XYZ", "email": "owner@toko-xyz.com", "kode": "CLT-1",
    "pic": "Budi", "no_telp_pic": "08123456789", "email_pic": "budi@toko-xyz.com",
    "alamat": "Jl. Merdeka 1", "norek": "1234567890", "bank": "BCA",
    "status": "approved", "has_developer": true, "is_auto_approve_withdrawal": false,
    "saldo": 226687500, "saldo_yang_bisa_ditarik": 200000000
  }
}
# Perbarui field yang boleh diubah (semua opsional)
curl -X PUT .../api/v2/account \
  -H "X-API-Key: $SARANA_API_KEY" -H "Content-Type: application/json" \
  -d '{ "norek": "9876543210", "bank": "BCA", "pic": "Budi Santoso" }'

Field yang boleh diubah: nama, email, alamat, pic, no_telp_pic, email_pic, norek, bank. Field terkunci admin — id, kode, balance, has_developer, is_auto_approve_withdrawal, plan/subscription — diabaikan bila dikirim (tidak akan mengubah data). bank harus salah satu dari GET /api/v2/bank-channels/active, norek numeric.

Saldo saja: GET /api/v2/account/balance (alias GET /api/v2/clients/{id}/balance).

7. Menghitung biaya (fees)

Hitung biaya sebelum membuat transaksi:

curl -X POST .../api/v2/fees/calculate \
  -H "X-API-Key: $SARANA_API_KEY" -H "Content-Type: application/json" \
  -d '{ "client_id": 123, "amount": 150000, "payment_method": "VA" }'

Endpoint fee lain: /fees/rates, /fees/calculate/bulk, /fees/calculate/vat, /fees/calculate/withdrawal, /fees/calculate/amount-to-pay. Lihat Swagger untuk parameter masing-masing.

8. Transaksi & saldo

# List transaksi (paginated: ?page=1&per_page=20)
curl ".../api/v2/transactions?page=1&per_page=20" -H "X-API-Key: $SARANA_API_KEY"

# Detail per id internal
curl .../api/v2/transactions/{id} -H "X-API-Key: $SARANA_API_KEY"

# Cari berdasarkan external id Anda
curl .../api/v2/transactions/external/{externalId} -H "X-API-Key: $SARANA_API_KEY"

# Saldo client
curl .../api/v2/clients/{id}/balance -H "X-API-Key: $SARANA_API_KEY"

9. Penarikan dana (withdrawals) & settlement

# Buat penarikan
curl -X POST .../api/v2/withdrawals \
  -H "X-API-Key: $SARANA_API_KEY" -H "Content-Type: application/json" \
  -d '{ "client_id": 123, "amount": 1000000 }'

# Cek / batalkan
curl .../api/v2/withdrawals/{id} -H "X-API-Key: $SARANA_API_KEY"
curl -X POST .../api/v2/withdrawals/{id}/cancel -H "X-API-Key: $SARANA_API_KEY"

# Settlement
curl -X POST .../api/v2/settlements \
  -H "X-API-Key: $SARANA_API_KEY" -H "Content-Type: application/json" \
  -d '{ "client_id": 123, "amount": 500000 }'

Daftar bank untuk penarikan: GET .../api/v2/bank-channels/active.

Bentuk body persis (field opsional, validasi norek/bank) ada di Swagger.

10. Webhook / callback

Sarana mengirim notifikasi status ke endpoint Anda. Jenis callback yang tersedia (lihat Swagger bagian Callbacks): pembayaran/invoice, transaksi, withdrawal, disbursement. Selalu balas cepat (2xx) dan proses secara asinkron.

Callback yang di-forward Sarana ke callback_url Anda tidak membawa header X-Callback-Token. Token itu hanya dipakai di leg Xendit→Sarana. Amankan endpoint Anda dengan IP allowlist / URL rahasia, bukan mengecek token pada payload ini.

Callback pembayaran (invoice)

Saat status invoice berubah ke terminal (PAID/SETTLED, atau EXPIRED), Sarana POST ke callback_url yang Anda sertakan di metadata waktu membuat invoice. Bila metadata.type_transaksi diisi, Sarana menambahkan query ?via=<type_transaksi> ke URL.

{
  "id": "6a52ff4de3f0aa9c76feb053",
  "external_id": "INV-11-1720771200000000000",
  "transaction_id": "INV-11-1720771200000000000",
  "client_external_id": "ORDER-2026-0042",
  "user_id": "5f2d…",
  "status": "PAID",
  "amount": 250000,
  "paid_amount": 250000,
  "adjusted_received_amount": 247500,
  "paid_at": "2026-07-12T09:14:03.123Z",
  "payment_method": "BANK_TRANSFER",
  "payment_channel": "BCA",
  "biayavat": 2500,
  "metadata": { "callback_url": "https://…", "type_transaksi": "topup" }
}
fieldarti
idXendit invoice id (hex)
transaction_idreferensi transaksi Sarana — INV-<client>-<nano>
external_idsama dengan transaction_id (nilai yang dikirim Sarana ke Xendit)
client_external_idexternal_id yang Anda kirim saat membuat invoice (kosong bila tak diisi)
statusstatus Xendit mentah, UPPERCASE: PAID, SETTLED, atau EXPIRED — normalkan sendiri
amount / paid_amountnominal tagihan / dibayar
adjusted_received_amountnominal bersih diterima (setelah fee)
biayavatbiaya + VAT yang dihitung Sarana

Matching: cocokkan pada transaction_id (INV-…, ambil dari response create invoice) atau client_external_id (referensi Anda sendiri). Jangan andalkan id numerik — id selalu string hex Xendit.

Penting — status non-lunas: Sarana hanya mem-forward PAID/SETTLED dan EXPIRED. Untuk status lain (mis. pending yang tak pernah dibayar) poll GET /api/v2/payments/{id}/status atau GET /api/v2/transactions/external/{clientExternalId}; jangan menunggu callback.

Kirim external_id Anda saat membuat invoice supaya bisa dicari & muncul di callback:

curl -X POST .../api/v2/payments/invoice \
  -H "X-API-Key: $SARANA_API_KEY" -H "Content-Type: application/json" \
  -d '{ "app_id": 45, "amount": 250000, "external_id": "ORDER-2026-0042",
        "metadata": { "callback_url": "https://toko-anda.com/callback", "type_transaksi": "topup" } }'

Callback penarikan (withdrawal)

Saat penarikan selesai ditransfer, Sarana mengirim POST ke url yang Anda sertakan waktu membuat withdrawal. Body persis tiga field:

{
  "status": "completed",
  "di_transfer_oleh": "Xendit",
  "transfer_at": "2026-07-10T17:20:21+07:00"
}
fieldarti
statusstatus akhir penarikan — completed atau failed
di_transfer_olehsiapa yang mentransfer
transfer_atwaktu transfer (ISO-8601)

Endpoint callback Anda harus menerima ketiga field ini apa adanya dan membalas 2xx. Jangan mewajibkan field lain (mis. id, jumlah) — payload memang hanya berisi tiga field di atas. Callback dikirim asinkron dengan satu kali retry bila gagal.

11. Ringkas

  1. Dapatkan API key (portal bila punya akses developer, atau minta admin). Satu key per-client untuk semua apps.
  2. Kirim X-API-Key di setiap request ke …/api/v2/…. client_id otomatis dari key.
  3. Ambil app_id via GET /api/v2/apps; sertakan di request pembayaran bila app lebih dari satu.
  4. Buat pembayaran → tampilkan instruksi ke pembeli → tunggu callback / poll status.
  5. Tarik saldo via /withdrawals.
  6. Uji dulu di sandbox (api.internal-go.…), lalu pindah production (api.internal.…) — path & payload sama, ganti host + API key.
  7. Referensi lengkap & coba langsung: /api/v2/docs.