1. Mulai cepat
- Daftar merchant di halaman daftar dan selesaikan KYC.
- Ambil API Key + Webhook Secret dari dashboard (setelah akun aktif).
- Uji di sandbox, lalu ganti base URL ke produksi.
- Pasang endpoint webhook yang memverifikasi signature sebelum mengubah status order.
2. Autentikasi
Setiap request ke API memakai Bearer token di header Authorization.
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Idempotency-Key: <uuid-unik-opsional>
Idempotency-Keydisarankan padaPOSTagar retry jaringan tidak membuat invoice dobel.- Key produksi dan sandbox berbeda. Jangan saling tukar.
- Kalau key bocor: revoke segera di dashboard, buat key baru.
3. Lingkungan
| Lingkungan | Base URL | Uang nyata |
|---|---|---|
| Sandbox | https://api-sandbox.naysyam.com | Tidak |
| Produksi | https://api.naysyam.com | Ya |
Halaman pembayaran customer:
- Sandbox:
https://pay-sandbox.naysyam.com/invoice/{id} - Produksi:
https://pay.naysyam.com/invoice/{id}
4. Buat invoice
POST /v1/invoices
Body
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
| external_id | string | Ya | ID unik di sistemmu (maks. 64 karakter) |
| amount | integer | Ya | Nominal IDR, tanpa desimal (contoh 1250000) |
| description | string | Ya | Tampil di halaman bayar |
| channel | string[] | Tidak | Filter kanal. Kosong = semua kanal aktif akun |
| customer.email | string | Tidak | Untuk notifikasi & rekonsiliasi |
| customer.phone | string | Tidak | Format 62… |
| success_url | string | Tidak | Redirect setelah bayar sukses |
| failure_url | string | Tidak | Redirect jika gagal / dibatalkan |
| expires_in | integer | Tidak | Detik hingga kedaluwarsa (default 86400) |
| metadata | object | Tidak | Data bebas, dikembalikan di webhook |
curl -X POST "https://api.naysyam.com/v1/invoices" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9f3c2a1b-4d55-4e12-9c01-88aa77bb66cc" \
-d '{
"external_id": "order-2291",
"amount": 1250000,
"description": "Pembayaran order #2291",
"channel": ["qris", "bca_va", "gopay"],
"customer": { "email": "[email protected]", "phone": "6281227125165" },
"success_url": "https://tokokamu.com/bayar/sukses",
"failure_url": "https://tokokamu.com/bayar/gagal",
"metadata": { "order_id": "2291" }
}'
Respons 201
{
"id": "inv_01JABCDEF123",
"external_id": "order-2291",
"status": "pending",
"amount": 1250000,
"currency": "IDR",
"checkout_url": "https://pay.naysyam.com/invoice/inv_01JABCDEF123",
"expires_at": "2026-10-30T12:00:00+07:00",
"created_at": "2026-10-29T12:00:00+07:00"
}
Arahkan customer ke checkout_url. Jangan mengarang URL sendiri.
5. Ambil satu invoice
GET /v1/invoices/{id} atau /v1/invoices?external_id=order-2291
curl "https://api.naysyam.com/v1/invoices/inv_01JABCDEF123" \
-H "Authorization: Bearer YOUR_API_KEY"
Status yang mungkin: pending, paid, expired, failed, refunded.
6. Daftar invoice
GET /v1/invoices?limit=20&cursor=…&status=paid
limit1–100 (default 20)- Paginasi cursor-based — pakai
next_cursordari respons - Filter opsional:
status,created_from,created_to
7. Halaman pembayaran
Customer membayar di hosted checkout NaySyam (pay.naysyam.com). Data kartu tidak pernah menyentuh server merchant — diproses pemroses berizin PCI-DSS.
- Jangan iframe URL bayar tanpa izin tertulis (beberapa kanal memblokir iframe).
- Setelah sukses, andalkan webhook sebagai sumber kebenaran, bukan hanya redirect browser.
- Redirect
success_urlbisa digagalkan user; webhook tetap jalan.
8. Webhook
NaySyam mengirim POST JSON ke URL webhook yang kamu daftarkan saat event terjadi (mis. invoice dibayar).
Header
Content-Type: application/jsonX-NaySyam-Signature: <hex hmac-sha256>X-NaySyam-Timestamp: <unix-seconds>X-NaySyam-Event: invoice.paidX-NaySyam-Delivery: <uuid>— pakai untuk idempotensi di sisi kamu
Payload contoh invoice.paid
{
"event": "invoice.paid",
"id": "inv_01JABCDEF123",
"external_id": "order-2291",
"status": "paid",
"amount": 1250000,
"paid_amount": 1250000,
"currency": "IDR",
"channel": "qris",
"paid_at": "2026-10-29T12:05:11+07:00",
"metadata": { "order_id": "2291" }
}
Perilaku retry
Jika endpointmu tidak mengembalikan 2xx dalam 5 detik, pengiriman diulang dengan backoff (hingga beberapa jam). Endpoint harus idempotent: event yang sama boleh datang lebih dari sekali.
200. Jangan proses berat sinkron di handler webhook.
9. Verifikasi HMAC-SHA256
Hitung HMAC atas string timestamp + "." + rawBody dengan Webhook Secret, bandingkan dengan header signature memakai perbandingan waktu-konstan.
const crypto = require("crypto");
function verifyNaySyamWebhook(rawBody, signature, timestamp, secret) {
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(age) || age > 300) return false; // tolak replay > 5 menit
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected, "utf8");
const b = Buffer.from(String(signature || ""), "utf8");
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}
// Express: pakai express.raw({ type: "application/json" }) agar rawBody utuh
app.post("/webhooks/naysyam", (req, res) => {
const ok = verifyNaySyamWebhook(
req.body.toString("utf8"),
req.get("X-NaySyam-Signature"),
req.get("X-NaySyam-Timestamp"),
process.env.NAYSYAM_WEBHOOK_SECRET
);
if (!ok) return res.status(401).send("invalid signature");
// proses event secara idempotent…
res.sendStatus(200);
});
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_NAYSYAM_SIGNATURE'] ?? '';
$ts = $_SERVER['HTTP_X_NAYSYAM_TIMESTAMP'] ?? '';
$secret = getenv('NAYSYAM_WEBHOOK_SECRET');
if (abs(time() - intval($ts)) > 300) { http_response_code(401); exit; }
$expected = hash_hmac('sha256', $ts . '.' . $raw, $secret);
if (!hash_equals($expected, $sig)) { http_response_code(401); exit; }
// proses…
10. Kode error
| HTTP | code | Arti |
|---|---|---|
| 400 | validation_error | Body/field tidak valid |
| 401 | unauthorized | API key hilang/salah |
| 403 | forbidden | Akun ditangguhkan / belum KYC |
| 404 | not_found | Resource tidak ada |
| 409 | conflict | external_id sudah dipakai |
| 429 | rate_limited | Terlalu banyak request |
| 500 | internal_error | Gangguan sementara — retry dengan backoff |
{
"error": {
"code": "validation_error",
"message": "amount must be at least 1000",
"param": "amount"
}
}
11. Rate limit
- Default: 120 request / menit / API key (burst singkat diizinkan).
- Header respons:
X-RateLimit-Limit,X-RateLimit-Remaining,Retry-Aftersaat 429. - Hormati 429 — jangan spam retry.
12. Keamanan (wajib dibaca)
- Simpan API key & webhook secret di environment variable / secret manager — bukan di repo publik.
- Verifikasi signature webhook + cek timestamp (anti-replay).
- Update status order hanya setelah webhook valid (atau GET invoice mengonfirmasi
paid). - Gunakan HTTPS di semua endpoint webhook kamu.
- Jangan log full API key / raw card data (kartu tidak boleh masuk sistemmu).
- Batasi IP admin dashboard, aktifkan 2FA saat tersedia.
- Pisahkan kredensial sandbox vs produksi.
Yang NaySyam jaga di sisi kami
- TLS 1.2+ di edge (Cloudflare) dan origin.
- Header keamanan: CSP, HSTS, X-Content-Type-Options, Referrer-Policy.
- File sensitif (.env, .md internal, key) diblokir di web root.
- Data kartu diproses pemroses berizin — tidak disimpan di frontend merchant.
13. Checklist go-live
- KYC merchant disetujui
- Webhook URL HTTPS + verifikasi HMAC lolos uji
- Idempotensi order teruji (delivery berulang tidak dobel fulfill)
- success_url / failure_url benar
- Rekonsiliasi harian (export / daftar invoice) disiapkan
- Kontak darurat teknis tercatat di dashboard
Butuh bantuan integrasi? WhatsApp 0812-2712-5165 · [email protected]