# Autentikasi & Penandatanganan Permintaan

> Tandatangani permintaan API dengan HMAC-SHA256 menggunakan project UUID dan API key Anda.

Setiap permintaan API (kecuali webhook masuk) harus membawa project UUID dan tanda tangan permintaan Anda. Tanda tangan membuktikan bahwa permintaan berasal dari Anda dan tidak ada yang mengubahnya selama pengiriman.

## API key

2328.io menggunakan **dua key** yang berbagi algoritma penandatanganan yang sama tetapi mencakup endpoint yang berbeda:

| Key | Digunakan untuk |
|-----|----------|
| **API key** | Pembayaran, dompet statis, saldo, nilai tukar, dan verifikasi webhook pembayaran / dompet statis |
| **Payout API key** | Semua endpoint `/v1/payout/*` dan verifikasi webhook penarikan |

Kedua key tersedia di pengaturan project Anda di [2328.io](https://2328.io). Contoh di bawah menyebut "API key" secara umum — gantikan dengan key yang sesuai untuk endpoint yang Anda panggil.

> **INFO:** **Jangan pernah** mencampur kedua key: menandatangani permintaan penarikan dengan API key biasa (atau permintaan pembayaran dengan payout key) akan mengembalikan kesalahan tanda tangan.

## Header yang diperlukan

| Header | Tipe | Wajib | Deskripsi |
|--------|------|----------|-------------|
| `Content-Type` | string | ya | Selalu `application/json` |
| `project` | string | ya | Project UUID Anda |
| `sign` | string | ya | Tanda tangan HMAC-SHA256 dari permintaan, dihitung dengan API key Anda |
| `User-Agent` | string | ya | Mengidentifikasi aplikasi Anda (mis. `MyShop/1.4 (+https://myshop.example)`). Permintaan tanpa `User-Agent` dapat diblokir. |

## Cara kerja tanda tangan

Anggap tanda tangan sebagai sidik jari dari body permintaan. Dibuat dengan:

1. Serialisasi body ke JSON (kompak — tanpa spasi tambahan).
2. Encode JSON tersebut ke Base64. Langkah ini menormalkan input lintas bahasa — setelah menjadi ASCII murni, setiap bahasa menghasilkan byte yang sama untuk HMAC.
3. Hitung **HMAC-SHA256** dari string Base64 menggunakan API key Anda, lalu konversi hasilnya ke huruf kecil hex.

Untuk **GET** dan tipe permintaan lain tanpa body, tandatangani string kosong sebagai pengganti JSON.

> **INFO:** Tanda tangan string kosong bersifat konstan untuk API key tertentu. Anda dapat menyimpannya di cache jika melakukan banyak panggilan GET.

## Implementasi

<CodeSnippet name="apiSign" langs="php,js,ts,python,go" />

### Permintaan tanpa body (GET)

Untuk permintaan tanpa body (mis. `GET /v1/payout/status/{uuid}`), tandatangani string kosong:

<CodeSnippet name="apiSignBodyless" langs="curl,php,js,ts,python,go" />

## Contoh permintaan lengkap

<CodeSnippet name="fullRequestExample" langs="curl,php,js,ts,python,go" />

> **DANGER:** **Jangan pernah mengekspos API key Anda di kode sisi klien.** Tandatangani permintaan di backend Anda. API key yang bocor memberi siapa pun akses penuh ke akun merchant Anda.

## Memverifikasi tanda tangan webhook

Saat 2328.io mengirim webhook kepada Anda, algoritma yang sama berjalan secara terbalik:

1. Tarik field `sign` dari payload.
2. Encode field yang tersisa ke JSON (kompak, tanpa spasi).
3. Encode string tersebut ke Base64.
4. Hitung `HMAC-SHA256` dengan key yang sesuai.
5. Bandingkan dengan `sign` yang diterima menggunakan perbandingan **constant-time** (`hash_equals`, `crypto.timingSafeEqual`, `hmac.compare_digest`, `subtle.ConstantTimeCompare`, `OpenSSL.fixed_length_secure_compare`).

Key penandatanganan tergantung pada sumber webhook:

| Webhook | Key untuk verifikasi |
|---------|---------------------|
| Webhook pembayaran / dompet statis (`/v1/payment`, `/v1/static-wallet`) | **API key** |
| Webhook penarikan (`/v1/payout`) | **Payout API key** |

> **WARNING:** **Kesalahan umum verifikasi.** Encoder JSON Anda harus menghasilkan **byte yang persis sama** dengan yang dihasilkan pengirim — jika tidak, Base64 akan berbeda dan tanda tangan tidak akan cocok.

- **Go**: gunakan `json.NewEncoder` dengan `SetEscapeHTML(false)`. `json.Marshal` default melakukan escape `<`, `>`, `&` menjadi `<` dan merusak tanda tangan.
- **Python**: berikan `ensure_ascii=False` ke `json.dumps`. Tanpa itu, karakter non-ASCII (Sirilik, Tionghoa, …) di-escape menjadi `\uXXXX`.
- **JSON ringkas**: tanpa spasi antar field (`separators=(",", ":")` di Python).
- **Urutan field** (Go): `map[string]any` biasa mengacak kunci saat di-encode ulang. Gunakan `json.RawMessage`, struct yang berurutan, atau hapus `sign` dari byte mentah.

Jika verifikasi terus gagal, jalankan `apiSign` pada payload Anda sendiri — hasilnya harus berupa string heksadesimal yang sama dengan `sign` yang diterima.

> **INFO:** **Tanda tangan yang valid tidak mencegah replay.** Tanda tangan hanya membuktikan bahwa webhook berasal dari 2328.io — bukan menghentikan penyerang untuk mengirim ulang webhook yang *telah ditangkap* belakangan. Selalu periksa idempotensi berdasarkan `uuid` (atau `txid` untuk dompet statis) sebelum mengkreditkan dana. Tolak dengan HTTP `401` jika tanda tangan hilang atau salah.

Contoh kode lengkap ada di **[Webhook Notifications](/docs/webhooks#verifying-the-signature)**. Penanganan retry dan aturan idempotensi ada di [Praktik terbaik](/docs/webhooks#best-practices).