Syah Topup
REST API v1

Dokumentasi API Syah Topup

Hubungkan website atau bot kamu ke Syah Topup: ambil daftar game & harga, cek ID game, buat pesanan top up otomatis (dibayar dari saldo akunmu), lalu terima hasilnya lewat webhook.

Base URL. Semua endpoint di bawah relatif terhadap alamat ini.

Mulai cepat

  1. Punya akun & saldo. Pesanan lewat API dibayar dari saldo akunmu. Daftar lalu isi saldo lewat menu Deposit.
  2. Buat API key di halaman API & Webhook. Key berawalan syah- dan hanya tampil sekali, simpan baik-baik.
  3. Panggil API dari server/bot kamu, kirim key lewat header X-API-Key.
  4. Isi URL webhook (opsional) supaya hasil pesanan dikirim otomatis ke server kamu, lengkap dengan tanda tangan syah- secret.

Contoh request

const axios = require('axios');

const BASE_URL = 'https://syah-topup.my.id/api/v1';
const API_KEY = 'YOUR_API_KEY';

axios
  .get(`${BASE_URL}/profile`, {
    headers: { 'X-API-Key': API_KEY },
  })
  .then((res) => console.log(res.data))
  .catch((err) => console.log(err.response ? err.response.data : err.message));
Response · HTTP 200
{
  "success": true,
  "data": {
    "username": "budi",
    "name": "Budi",
    "balance": 150000,
    "api_key": {
      "name": "Bot Telegram",
      "hint": "syah-3fa9••••••••c21d",
      "created_at": "2026-11-03T08:00:00.000Z",
      "webhook_enabled": true
    },
    "rate_limit_per_minute": 60
  }
}

Autentikasi

Setiap request wajib membawa API key. Pilih salah satu:

X-API-Key: syah-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Authorization: Bearer syah-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • Format key: syah- diikuti 48 karakter heksadesimal. Secret webhook memakai awalan yang sama.
  • Panggil API hanya dari server atau bot. Jangan taruh key di JavaScript browser, aplikasi mobile, atau repository publik.
  • Key disimpan dalam bentuk hash di server kami, jadi tidak bisa ditampilkan ulang. Kalau hilang atau bocor, ganti key atau cabut lewat halaman API & Webhook.
  • Opsional: batasi key ke alamat IP server kamu (IP whitelist). Request dari IP lain ditolak dengan kode ip_not_allowed.

Format respons & batas

Semua respons berbentuk JSON. Request POST mengirim body JSON dengan header Content-Type: application/json.

{
  "success": true,
  "data": { }
}

{
  "success": false,
  "error": {
    "code": "insufficient_balance",
    "message": "Saldo tidak cukup. Dibutuhkan Rp20.500."
  }
}
  • Batas 60 request per menit per API key. Sisa kuota ada di header X-RateLimit-Remaining; saat habis kamu menerima HTTP 429 dengan header Retry-After.
  • POST /check-id dibatasi 20 request per menit dan POST /orders 30 request per menit per key.
  • Semua harga dalam Rupiah (angka bulat, tanpa titik). Semua waktu dalam format ISO 8601 UTC.
  • Kode HTTP: 200/201 berhasil, 400/422 input salah, 401/403 key atau akses bermasalah, 402 saldo kurang, 404 tidak ditemukan, 429 kena batas, 5xx masalah di server.

Akun & saldo

GET/profile

Info akun pemilik key, saldo saat ini, dan batas request.

Contoh request

const axios = require('axios');

const BASE_URL = 'https://syah-topup.my.id/api/v1';
const API_KEY = 'YOUR_API_KEY';

axios
  .get(`${BASE_URL}/profile`, {
    headers: { 'X-API-Key': API_KEY },
  })
  .then((res) => console.log(res.data))
  .catch((err) => console.log(err.response ? err.response.data : err.message));

Contoh response

Response · HTTP 200
{
  "success": true,
  "data": {
    "username": "budi",
    "name": "Budi",
    "balance": 150000,
    "api_key": {
      "name": "Bot Telegram",
      "hint": "syah-3fa9••••••••c21d",
      "created_at": "2026-11-03T08:00:00.000Z",
      "webhook_enabled": true
    },
    "rate_limit_per_minute": 60
  }
}
GET/balance

Hanya saldo akun.

Contoh request

const axios = require('axios');

const BASE_URL = 'https://syah-topup.my.id/api/v1';
const API_KEY = 'YOUR_API_KEY';

axios
  .get(`${BASE_URL}/balance`, {
    headers: { 'X-API-Key': API_KEY },
  })
  .then((res) => console.log(res.data))
  .catch((err) => console.log(err.response ? err.response.data : err.message));

Contoh response

Response · HTTP 200
{
  "success": true,
  "data": {
    "balance": 150000
  }
}

Game & produk

GET/games

Daftar game yang tersedia beserta harga mulai dari.

Contoh request

const axios = require('axios');

const BASE_URL = 'https://syah-topup.my.id/api/v1';
const API_KEY = 'YOUR_API_KEY';

axios
  .get(`${BASE_URL}/games`, {
    headers: { 'X-API-Key': API_KEY },
  })
  .then((res) => console.log(res.data))
  .catch((err) => console.log(err.response ? err.response.data : err.message));

Contoh response

Response · HTTP 200
{
  "success": true,
  "data": [
    {
      "slug": "mobile-legends",
      "name": "Mobile Legends: Bang Bang",
      "category": "MOBA",
      "requires_server_id": true,
      "id_label": "User ID",
      "server_label": "Zone ID",
      "is_voucher": false,
      "product_count": 24,
      "min_price": 3500
    }
  ]
}
GET/games/{slug}

Detail satu game, termasuk semua produk aktif dan harga jualnya. Pakai code produk saat membuat pesanan.

Contoh request

const axios = require('axios');

const BASE_URL = 'https://syah-topup.my.id/api/v1';
const API_KEY = 'YOUR_API_KEY';

axios
  .get(`${BASE_URL}/games/mobile-legends`, {
    headers: { 'X-API-Key': API_KEY },
  })
  .then((res) => console.log(res.data))
  .catch((err) => console.log(err.response ? err.response.data : err.message));

Contoh response

Response · HTTP 200
{
  "success": true,
  "data": {
    "slug": "mobile-legends",
    "name": "Mobile Legends: Bang Bang",
    "requires_server_id": true,
    "id_label": "User ID",
    "server_label": "Zone ID",
    "id_check": true,
    "products": [
      {
        "code": "ML86",
        "name": "86 Diamonds",
        "price": 21000
      },
      {
        "code": "ML172",
        "name": "172 Diamonds",
        "price": 41500
      }
    ]
  }
}

Harga bisa berubah mengikuti pembaruan katalog. Harga yang dipotong dari saldo adalah harga saat pesanan dibuat, dan selalu dihitung ulang di server.

Cek ID game

POST/check-id

Cek nickname pemain sebelum membuat pesanan. Gratis, tidak memotong saldo.

FieldWajibKeterangan
gameyaSlug game, mis. mobile-legends
user_idyaID pemain
server_idjika game memakai serverZone/Server ID

Contoh request

const axios = require('axios');

const BASE_URL = 'https://syah-topup.my.id/api/v1';
const API_KEY = 'YOUR_API_KEY';

axios
  .post(`${BASE_URL}/check-id`, {
    game: 'mobile-legends',
    user_id: '12345678',
    server_id: '2001'
  }, {
    headers: { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' },
  })
  .then((res) => console.log(res.data))
  .catch((err) => console.log(err.response ? err.response.data : err.message));

Contoh response

Response · HTTP 200
{
  "success": true,
  "data": {
    "status": "ok",
    "nickname": "BudiGamer",
    "message": null
  }
}

status bernilai ok (ID ditemukan), notfound (ID jelas tidak ada), error (layanan cek bermasalah, pembelian tetap bisa dilanjutkan), atau unsupported (game tidak punya cek ID).

Pesanan

POST/orders

Buat pesanan top up. Harga langsung dipotong dari saldo akunmu, lalu pesanan diproses otomatis. Respons pertama bisa berstatus pending, processing, atau langsung success; hasil akhir dikirim lewat webhook atau bisa kamu cek lewat GET /orders/{id}.

FieldWajibKeterangan
product_codeyaKode produk dari GET /games/{slug}
user_idyaID pemain. Untuk game voucher: nomor HP atau email penerima
server_idjika game memakai serverZone/Server ID
ref_iddisarankanID unik dari sistem kamu (maks 64 karakter: huruf, angka, . - _). Mencegah pesanan ganda saat request diulang
gametidakSlug game. Kalau diisi, harus cocok dengan game milik produk

Contoh request

const axios = require('axios');

const BASE_URL = 'https://syah-topup.my.id/api/v1';
const API_KEY = 'YOUR_API_KEY';

axios
  .post(`${BASE_URL}/orders`, {
    product_code: 'ML86',
    user_id: '12345678',
    server_id: '2001',
    ref_id: 'INV-1001'
  }, {
    headers: { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' },
  })
  .then((res) => console.log(res.data))
  .catch((err) => console.log(err.response ? err.response.data : err.message));

Contoh response

Response · HTTP 201
{
  "success": true,
  "duplicate": false,
  "data": {
    "order_id": "SYMK2F9A1B7C",
    "ref_id": "INV-1001",
    "status": "processing",
    "message": "Pesanan sedang diproses.",
    "game": "mobile-legends",
    "game_name": "Mobile Legends: Bang Bang",
    "product_code": "ML86",
    "product_name": "86 Diamonds",
    "user_id": "12345678",
    "server_id": "2001",
    "nickname": "BudiGamer",
    "price": 21000,
    "sn": null,
    "created_at": "2026-11-03T08:15:00.000Z",
    "paid_at": "2026-11-03T08:15:00.000Z",
    "updated_at": "2026-11-03T08:15:02.000Z"
  }
}

Idempotensi: kalau ref_id yang sama dikirim lagi, pesanan lama dikembalikan dengan HTTP 200 dan "duplicate": true, saldo tidak dipotong lagi. Selalu kirim ref_id supaya aman saat koneksi putus dan request diulang.

GET/orders/{id}

{id} boleh berupa order_id atau ref_id. Mengembalikan objek pesanan seperti di atas, dengan status terbaru dan nomor serial (sn) saat tersedia.

Contoh request

const axios = require('axios');

const BASE_URL = 'https://syah-topup.my.id/api/v1';
const API_KEY = 'YOUR_API_KEY';

axios
  .get(`${BASE_URL}/orders/INV-1001`, {
    headers: { 'X-API-Key': API_KEY },
  })
  .then((res) => console.log(res.data))
  .catch((err) => console.log(err.response ? err.response.data : err.message));

Contoh response

Response · HTTP 200
{
  "success": true,
  "data": {
    "order_id": "SYMK2F9A1B7C",
    "ref_id": "INV-1001",
    "status": "success",
    "message": "Top up berhasil dikirim.",
    "game": "mobile-legends",
    "game_name": "Mobile Legends: Bang Bang",
    "product_code": "ML86",
    "product_name": "86 Diamonds",
    "user_id": "12345678",
    "server_id": "2001",
    "nickname": "BudiGamer",
    "price": 21000,
    "sn": "1234567890",
    "created_at": "2026-11-03T08:15:00.000Z",
    "paid_at": "2026-11-03T08:15:00.000Z",
    "updated_at": "2026-11-03T08:15:09.000Z"
  }
}
GET/orders

Daftar pesanan yang dibuat lewat API, terbaru dulu. Query: page (default 1), limit (default 20, maks 50), status (opsional).

Contoh request

const axios = require('axios');

const BASE_URL = 'https://syah-topup.my.id/api/v1';
const API_KEY = 'YOUR_API_KEY';

axios
  .get(`${BASE_URL}/orders`, {
    headers: { 'X-API-Key': API_KEY },
    params: {
      page: 1,
      limit: 20,
      status: 'success'
    },
  })
  .then((res) => console.log(res.data))
  .catch((err) => console.log(err.response ? err.response.data : err.message));

Contoh response

Response · HTTP 200
{
  "success": true,
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 42,
    "pages": 3
  },
  "data": [
    {
      "order_id": "SYMK2F9A1B7C",
      "ref_id": "INV-1001",
      "status": "success",
      "message": "Top up berhasil dikirim.",
      "game": "mobile-legends",
      "game_name": "Mobile Legends: Bang Bang",
      "product_code": "ML86",
      "product_name": "86 Diamonds",
      "user_id": "12345678",
      "server_id": "2001",
      "nickname": "BudiGamer",
      "price": 21000,
      "sn": "1234567890",
      "created_at": "2026-11-03T08:15:00.000Z",
      "paid_at": "2026-11-03T08:15:00.000Z",
      "updated_at": "2026-11-03T08:15:09.000Z"
    }
  ]
}

Webhook

Isi URL webhook di halaman API & Webhook dan kami akan mengirim POST JSON ke server kamu setiap kali pesanan API mencapai status akhir. Tiap API key punya secret webhook sendiri (berawalan syah-) untuk memverifikasi bahwa request benar-benar dari kami.

  • URL harus https://, dapat diakses dari internet, dan tidak boleh mengarah ke alamat lokal/privat.
  • Balas dengan HTTP 2xx dalam 8 detik. Respons lain atau timeout dianggap gagal dan dicoba ulang.
  • Pengiriman diulang otomatis sampai 7 kali (percobaan pertama + 6 ulangan, berjarak 10 dtk, 1 mnt, 5 mnt, 30 mnt, 2 jam, 6 jam). Karena itu server kamu harus siap menerima event yang sama lebih dari sekali: cegah duplikat dengan field id.
  • Redirect (3xx) tidak diikuti.

Event

EventKapan dikirim
order.successTop up berhasil dikirim ke pemain
order.failedTop up gagal dan dana belum dikembalikan
order.refundedTop up gagal dan dana sudah dikembalikan ke saldo
order.cancelledPesanan dibatalkan
webhook.testTes dari tombol "Tes webhook"

Header

HeaderIsi
X-Syah-EventNama event, mis. order.success
X-Syah-DeliveryID pengiriman (sama dengan field id di body)
X-Syah-TimestampWaktu kirim (detik Unix)
X-Syah-Signaturesha256= + HMAC-SHA256 heksadesimal

Body

Body
{
  "id": "whd_9c1d2e3f4a5b6c7d8e9f",
  "event": "order.success",
  "created_at": "2026-11-03T08:15:09.000Z",
  "data": {
    "order_id": "SYMK2F9A1B7C",
    "ref_id": "INV-1001",
    "status": "success",
    "message": "Top up berhasil dikirim.",
    "game": "mobile-legends",
    "game_name": "Mobile Legends: Bang Bang",
    "product_code": "ML86",
    "product_name": "86 Diamonds",
    "user_id": "12345678",
    "server_id": "2001",
    "nickname": "BudiGamer",
    "price": 21000,
    "sn": "1234567890",
    "created_at": "2026-11-03T08:15:00.000Z",
    "paid_at": "2026-11-03T08:15:00.000Z",
    "updated_at": "2026-11-03T08:15:09.000Z"
  }
}

Verifikasi tanda tangan

Hitung HMAC-SHA256 dengan secret webhook sebagai kunci atas teks timestamp + "." + body_mentah, lalu bandingkan dengan header X-Syah-Signature memakai perbandingan waktu-konstan. Pakai body mentah (belum di-parse), dan tolak request yang timestamp-nya lebih dari 5 menit.

const crypto = require('crypto');
const express = require('express');

const app = express();
const SECRET = 'YOUR_WEBHOOK_SECRET';

app.post('/syah-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.get('X-Syah-Timestamp') || '';
  const signature = req.get('X-Syah-Signature') || '';
  const body = req.body.toString('utf8');
  const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(timestamp + '.' + body).digest('hex');
  const a = Buffer.from(signature);
  const b = Buffer.from(expected);
  const valid = a.length === b.length && crypto.timingSafeEqual(a, b);
  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
  if (!valid || !fresh) return res.status(401).json({ ok: false });

  const evt = JSON.parse(body);
  console.log(evt.event, evt.data.order_id, evt.data.status);
  res.json({ ok: true });
});

app.listen(3000);

Secret webhook hanya tampil saat dibuat atau diputar ulang. Kalau lupa, putar ulang secret di halaman API & Webhook lalu perbarui di server kamu.

Status pesanan

StatusArtinyaFinal?
pendingPesanan diterima, menunggu giliran diprosestidak
processingSedang diproses ke penyedia produktidak
successTop up berhasil. Field sn berisi nomor serial bila tersediaya
failedTop up gagal. Dana belum kembali, hubungi admin bila perluya
refundedTop up gagal dan dana sudah dikembalikan ke saldo akunya
cancelledPesanan dibatalkanya

Jangan menganggap pesanan berhasil sebelum status success. Kalau webhook tidak sampai, cek lewat GET /orders/{id} dengan jeda beberapa detik, jangan membuat pesanan baru.

Kode error

HTTPerror.codePenyebab
400invalid_jsonBody bukan JSON yang valid
401missing_api_key, invalid_api_keyKey tidak dikirim, salah, atau sudah dicabut
402insufficient_balanceSaldo kurang. Respons memuat required dan balance
403ip_not_allowed, account_suspendedIP tidak ada di whitelist, atau akun ditangguhkan
404game_not_found, product_not_found, order_not_found, not_foundData tidak ada atau produk tidak dijual
413payload_too_largeBody terlalu besar
422invalid_request, invalid_user_id, invalid_server_id, invalid_ref_id, invalid_status, game_mismatch, id_not_foundInput tidak valid. id_not_found = ID game jelas tidak ada
429rate_limitedKena batas request. Tunggu sesuai header Retry-After
500server_errorKesalahan di server kami
503api_disabled, maintenanceAPI dimatikan admin atau website sedang perawatan

Alur untuk bot & website

  1. Ambil katalog dengan GET /games dan GET /games/{slug}, simpan sebentar di cache sisi kamu.
  2. Pembeli mengisi ID. Panggil POST /check-id dan tampilkan nickname untuk konfirmasi.
  3. Buat ref_id unik (mis. nomor invoice sistemmu), lalu POST /orders.
  4. Tunggu webhook order.success / order.refunded. Sebagai cadangan, cek GET /orders/{ref_id} tiap 10-30 detik sampai status final.
  5. Kalau POST /orders time out atau koneksi putus, ulangi dengan ref_id yang sama, jangan buat ref baru.
PT. Syah Support