Idempotensi
Jaringan tidak dapat diandalkan. Permintaan yang kamu kirim bisa saja sudah diterima dan diproses, lalu responsnya hilang di jalan. Tanpa mekanisme pengaman, percobaan ulang akan membuat pembayaran kedua — dan pelanggan tertagih dua kali. Idempotensi menghapus kemungkinan itu.
Kapan kunci diperlukan
| Endpoint | Kunci |
|---|---|
| POST /v1/payments | Wajib |
| POST /v1/refunds | Wajib |
| POST /v1/payments/{reference}/cancel | Wajib |
| GET /v1/* | Tidak perlu — sudah aman diulang |
Memilih nilai kunci
- Format bebas, panjang 8–255 karakter ASCII. UUID versi 4 adalah pilihan yang baik.
- Satu kunci untuk satu niat bisnis, bukan untuk satu percobaan HTTP. Bangkitkan kunci ketika pesanan dibuat, simpan bersama pesanan itu, lalu pakai kunci yang sama pada setiap percobaan ulang.
- Jangan membangkitkan kunci baru di dalam blok percobaan ulang. Itu justru menghilangkan perlindungannya.
- Jangan memakai satu kunci untuk dua operasi berbeda — misalnya nomor pesanan yang sama untuk pembayaran dan untuk refund.
# Benar — kunci melekat pada pesanan, bertahan lintas percobaan
order.idempotency_key = order.idempotency_key or str(uuid.uuid4())
order.save()
for attempt in range(5):
res = post("/payments", payload, idempotency_key=order.idempotency_key)
if res.status_code < 500:
break
time.sleep(2 ** attempt)
# Salah — kunci baru setiap percobaan; pembayaran ganda bisa terjadi
for attempt in range(5):
res = post("/payments", payload, idempotency_key=str(uuid.uuid4()))
Apa yang terjadi saat permintaan diulang
Kunci disimpan bersama sidik SHA-256 dari badan permintaan dan respons yang pernah dikembalikan. Ketika kunci yang sama datang lagi:
| Keadaan | HTTP | Yang terjadi |
|---|---|---|
| Kunci baru | 201 | Permintaan dieksekusi seperti biasa; hasilnya disimpan. |
| Kunci sama, badan identik, sudah selesai | 200 | Respons tersimpan dikembalikan apa adanya, disertai header X-Idempotent-Replay: true. Tidak ada eksekusi ulang, tidak ada uang yang bergerak dua kali. |
| Kunci sama, badan berbeda | 409 | IDEMPOTENCY_CONFLICT. Permintaan ditolak — memakai ulang kunci untuk isi yang berbeda hampir selalu berarti ada kekeliruan di sisi pemanggil. |
| Kunci sama, permintaan pertama masih berjalan | 409 | IDEMPOTENCY_IN_PROGRESS. Coba lagi setelah beberapa detik; jangan mengubah kuncinya. |
| Kunci tidak dikirim | 422 | IDEMPOTENCY_KEY_REQUIRED. Tidak ada yang dieksekusi. |
| Kunci melanggar format | 422 | IDEMPOTENCY_KEY_INVALID — terlalu pendek, terlalu panjang, atau mengandung karakter non-ASCII. |
Contoh respons konflik
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": {
"code": "IDEMPOTENCY_CONFLICT",
"message": "Idempotency-Key sudah dipakai untuk permintaan dengan isi berbeda.",
"request_id": "req_01J9Q4M2T7K3XW",
"details": {
"idempotency_key": "4f8c1f6e-2c1a-4d5b-9d61-3f0b7a2c9e14",
"first_seen_at": "2026-08-04T02:15:30Z"
}
}
}
Contoh respons pengulangan yang berhasil
HTTP/1.1 200 OK
X-Idempotent-Replay: true
Content-Type: application/json
{
"reference": "pay_01J9Q4M2T7K3XW",
"status": "pending",
"amount_minor": 150000,
"currency": "IDR",
"reference_id": "ORD-10427",
"environment": "sandbox",
"created_at": "2026-08-04T02:15:30Z"
}
Masa simpan kunci
Kunci beserta responsnya disimpan 72 jam sejak permintaan pertama. Setelah itu kunci yang sama dianggap baru dan permintaan akan dieksekusi lagi. Karena itu, percobaan ulang yang tertunda sangat lama sebaiknya diverifikasi dulu dengan GET /v1/payments?reference_id=… sebelum dikirim ulang.
Idempotensi berlapis
Kunci yang kamu kirim melindungi lapisan API. Di dalam sistem, setiap pergerakan dana membawa operation_key tersendiri pada buku besar. Artinya walaupun ada percobaan ulang di dalam sistem — misalnya karena batas waktu jaringan internal — jurnal yang sama tidak akan pernah dicatat dua kali.
Idempotency-Key (kamu -> API) pesan HTTP tidak dieksekusi dua kali
│
└── operation_key (API -> buku besar) jurnal tidak dicatat dua kali
│
└── entri debit + kredit jumlah selalu nol, hanya-tambah
Praktik percobaan ulang yang disarankan
- Ulangi hanya pada galat jaringan, batas waktu, 429, dan 5xx. Jangan mengulang 4xx lain — hasilnya akan sama.
- Gunakan jeda menaik dengan sedikit keacakan: 1 dtk, 2 dtk, 4 dtk, 8 dtk, maksimum lima percobaan.
- Setiap percobaan memakai X-Timestamp dan X-Signature yang baru, tetapi Idempotency-Key yang sama.
- Bila semua percobaan gagal, jangan menyimpulkan pembayaran gagal. Periksa statusnya lewat GET /v1/payments/{reference} atau tunggu webhook.

