Lewati ke konten utama

Bagaimana autentikasi API SekaliPay bekerja?

Setiap permintaan membawa API key di header X-API-Key. Selebihnya bergantung mode akun: IP Whitelist (bawaan) menerima permintaan dari IP terdaftar dan hanya menandatangani POST, sedangkan Enhanced HMAC menandatangani setiap permintaan beserta X-Timestamp dan cocok untuk server tanpa IP tetap.

Isi halaman ini dicocokkan dengan kode API per 16 Oktober 2026.

Apa beda API key dan secret key?#

KunciDipakai untukDikirim di permintaan?
API key (mk_...)Mengenali akun merchant Anda.Ya, di header X-API-Key setiap permintaan.
Secret key (sk_...)Menandatangani permintaan dan memverifikasi webhook.Tidak pernah. Hanya dipakai menghitung HMAC di server Anda.

Secret key hanya untuk server

Secret key tampil di dasbor setelah verifikasi PIN. Simpan di variabel lingkungan atau secret manager, jangan di aplikasi seluler, JavaScript browser, atau repositori. Bila bocor, buat kunci baru di Pengaturan API, API Keys: API key dan secret key diganti bersamaan dan kunci lama langsung tidak berlaku.

Mode mana yang sebaiknya dipakai?#

IP Whitelist (bawaan)Enhanced HMAC
Cocok untukServer dengan IP publik tetap: VPS, server kantor.IP berubah-ubah: Cloudflare Workers, AWS Lambda, Vercel.
Yang diperiksaIP pengirim harus terdaftar.Tanda tangan dan X-Timestamp. IP tidak diperiksa.
Permintaan GETCukup X-API-Key.X-API-Key, X-Signature, X-Timestamp.
Permintaan POSTX-API-Key dan X-Signature.X-API-Key, X-Signature, X-Timestamp.
Yang ditandatanganiBody JSON mentah.METHOD|PATH|PARAMS|TIMESTAMP.
Cara mengaktifkanIsi minimal satu IP di Pengaturan API, Mode & akses.Matikan IP whitelist di halaman yang sama.

Mode berlaku untuk seluruh akun dan tidak bisa dicampur per permintaan. Batas permintaan sama di kedua mode.

Bagaimana menandatangani permintaan di mode IP Whitelist?#

Hitung HMAC-SHA256 atas body JSON persis seperti yang dikirim, dengan secret key sebagai kunci, lalu kirim hasil hex huruf kecilnya di X-Signature. Permintaan GET tidak perlu tanda tangan.

Rumus
X-Signature = hex( HMAC-SHA256( secret_key, raw_request_body ) )
$body = json_encode([
    'merchant_ref_id' => 'ORDER-2026-00184',
    'amount'          => 150000,
    'payment_code'    => 'QRIS',
    'customer_name'   => 'Budi Santoso',
    'customer_email'  => '[email protected]',
    'customer_phone'  => '081234567890',
    'callback_url'    => 'https://toko-anda.id/webhook/sekalipay',
    'return_url'      => 'https://toko-anda.id/checkout/selesai',
]);

$signature = hash_hmac('sha256', $body, getenv('SEKALIPAY_SECRET_KEY'));

$ch = curl_init('https://sekalipay.com/api/v1/gateway/payment');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $body,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'Accept: application/json',
        'X-API-Key: ' . getenv('SEKALIPAY_API_KEY'),
        'X-Signature: ' . $signature,
    ],
]);

$response = json_decode(curl_exec($ch), true);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

Kesalahan paling umum

Menandatangani satu string lalu mengirim string lain, misalnya menandatangani hasil json_encode lalu membiarkan HTTP client meng-encode ulang array yang sama. Urutan kunci, spasi, atau escape \/ yang berbeda membuat tanda tangan tidak cocok. Simpan body sebagai string, tanda tangani string itu, kirim string itu.

Permintaan dari IP yang tidak terdaftar ditolak 403 IP_NOT_WHITELISTED, dan respons galatnya memuat your_ip: IP yang kami lihat, siap didaftarkan.

Bagaimana menandatangani permintaan di mode Enhanced HMAC?#

Setiap permintaan, termasuk GET, membawa X-Signature dan X-Timestamp. Yang ditandatangani adalah empat bagian yang digabung dengan |:

string_to_sign
string_to_sign = METHOD + "|" + PATH + "|" + PARAMS + "|" + TIMESTAMP
X-Signature    = hex( HMAC-SHA256( secret_key, string_to_sign ) )

POST|api/v1/gateway/payment|amount=50000&merchant_ref_id=ORDER-001&payment_code=QRIS|1791446400
GET|api/v1/gateway/payment/ORDER-001||1791446400
GET|api/v1/gateway/payment-methods||1791446400
BagianIsi
METHODHuruf besar: GET, POST.
PATHTanpa garis miring di depan dan tanpa query string: api/v1/gateway/payment.
PARAMSSemua parameter (field body JSON dan query string), kunci tingkat atas diurutkan A sampai Z, lalu di-encode persis seperti http_build_query() PHP. Kosong untuk GET tanpa parameter.
TIMESTAMPUnix time dalam detik, sama dengan header X-Timestamp. Selisih dengan jam server maksimal 300 detik.

Aturan encode PARAMS (penting untuk selain PHP)

Spasi menjadi +; hanya A-Z a-z 0-9 - _ . yang tetap, selebihnya %XX (hex huruf besar, byte UTF-8). Nilai null dan array kosong dihilangkan; true/false menjadi 1/0. Hanya kunci tingkat atas yang diurutkan; objek bertingkat seperti metadata mengikuti urutan kirim dan menjadi metadata%5Bkunci%5D=nilai. Server memangkas spasi di awal dan akhir setiap string dan membuang string kosong sebelum memverifikasi, jadi lakukan hal yang sama. Kedua contoh di bawah sudah menanganinya.

function phpUrlencode(value) {
  return encodeURIComponent(value)
    .replace(/[!'()*~]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase())
    .replace(/%20/g, "+");
}

function buildParams(params) {
  const parts = [];
  const add = (prefix, value) => {
    if (typeof value === "string") value = value.trim();
    if (value === null || value === undefined || value === "") return;
    if (typeof value === "object") {
      for (const key of Object.keys(value)) add(prefix + "%5B" + phpUrlencode(key) + "%5D", value[key]);
      return;
    }
    if (value === true) value = "1";
    else if (value === false) value = "0";
    parts.push(prefix + "=" + phpUrlencode(String(value)));
  };
  for (const key of Object.keys(params).sort()) add(phpUrlencode(key), params[key]);
  return parts.join("&");
}

async function hmacSha256Hex(secret, message) {
  const enc = new TextEncoder();
  const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
  const sig = await crypto.subtle.sign("HMAC", key, enc.encode(message));
  return [...new Uint8Array(sig)].map((b) => b.toString(16).padStart(2, "0")).join("");
}

export async function sekalipay(method, path, payload, { apiKey, secretKey }) {
  const timestamp = Math.floor(Date.now() / 1000);
  const stringToSign = method + "|" + path + "|" + buildParams(payload ?? {}) + "|" + timestamp;
  const signature = await hmacSha256Hex(secretKey, stringToSign);

  const res = await fetch("https://sekalipay.com/" + path, {
    method,
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
      "X-API-Key": apiKey,
      "X-Signature": signature,
      "X-Timestamp": String(timestamp),
    },
    body: method === "GET" ? undefined : JSON.stringify(payload),
  });

  return { status: res.status, body: await res.json() };
}

const keys = { apiKey: SEKALIPAY_API_KEY, secretKey: SEKALIPAY_SECRET_KEY };

await sekalipay("POST", "api/v1/gateway/payment", {
  merchant_ref_id: "ORDER-2026-00184",
  amount: 150000,
  payment_code: "QRIS",
  customer_name: "Budi Santoso",
  customer_email: "[email protected]",
  customer_phone: "081234567890",
}, keys);

await sekalipay("GET", "api/v1/gateway/payment/ORDER-2026-00184", null, keys);

Jam server yang meleset lebih dari 300 detik membuat semua permintaan ditolak 401 TIMESTAMP_EXPIRED. Respons galatnya memuat server_time untuk menyelaraskan jam.

401 TIMESTAMP_EXPIRED
{
  "success": false,
  "message": "Request timestamp expired or invalid",
  "error_code": "TIMESTAMP_EXPIRED",
  "data": {
    "server_time": 1791446812,
    "your_timestamp": 1791446400,
    "max_difference": 300
  }
}

Bagaimana memastikan tanda tangan saya sudah benar?#

Jalankan kode Anda dengan masukan di bawah. Hasilnya harus sama persis sebelum Anda mencoba API. Kunci ini contoh saja dan tidak berlaku di API.

IP Whitelist: HMAC atas body
secret_key  : sk_test_contoh_saja
body        : {"merchant_ref_id":"ORDER-001","amount":50000,"payment_code":"QRIS"}
X-Signature : f8fbd182f2c14ae0b8a231f0687e4070f095fe1881374f09f6a4634cf021fbfa
Enhanced HMAC: HMAC atas string_to_sign
secret_key     : sk_test_contoh_saja
X-Timestamp    : 1791446400
string_to_sign : POST|api/v1/gateway/payment|amount=50000&merchant_ref_id=ORDER-001&payment_code=QRIS|1791446400
X-Signature    : 46e51144b78aefcf73342111a465ddd6aede25abdcfbfdbb4c6cf1b792116402

Galat autentikasi apa yang mungkin muncul?#

HTTPKodeArti
401MISSING_API_KEYHeader X-API-Key tidak dikirim.
401INVALID_API_KEYAPI key salah, sudah diganti, atau akun tidak aktif.
403IP_WHITELIST_NOT_CONFIGUREDMode IP Whitelist aktif tetapi belum ada IP terdaftar. Tambahkan IP, atau matikan whitelist untuk memakai Enhanced HMAC.
403IP_NOT_WHITELISTEDIP pengirim tidak terdaftar. Daftarkan IP yang tertera di your_ip.
401MISSING_SIGNATUREPermintaan POST di mode IP Whitelist tanpa header X-Signature.
401INVALID_SIGNATURETanda tangan body tidak cocok. Tanda tangani byte yang persis sama dengan yang dikirim.
401MISSING_HMAC_SIGNATUREMode Enhanced HMAC aktif tetapi X-Signature atau X-Timestamp tidak dikirim.
401TIMESTAMP_EXPIREDX-Timestamp berselisih lebih dari 300 detik dari jam server. Bandingkan dengan data.server_time.
401INVALID_HMAC_SIGNATURETanda tangan Enhanced HMAC tidak cocok. Periksa PATH, urutan dan encode PARAMS, serta timestamp.

Siap memakai kunci Anda sendiri?

Masuk ke dasbor merchant untuk mengambil API key dan secret key, mengatur mode akses dan URL webhook, serta mengunduh koleksi Postman berisi kunci akun Anda.

Masuk ke dasbor