Status Pesanan (server-ke-server)
Referensi API status pesanan AdForm: baca satu pesanan dan pindahkan statusnya lewat kunci API. Scope, penguncian optimistik, idempotensi terikat isi permintaan, dan seluruh kode error.
Endpoint ini dipakai sistem lain untuk membaca satu pesanan dan memindahkan statusnya antara pending dan closing. Contoh pemakaian yang paling umum: aplikasi closing atau CRM tempat CS menutup deal, lalu status di AdForm ikut berpindah tanpa ada yang mengetik ulang.
Semua panggilan memakai kunci API yang diterbitkan penjual sendiri dari dashboard. Anda tidak pernah meminta kunci lewat WhatsApp, email, atau chat.
#Rangkuman satu layar
| Item | Nilai |
|---|---|
| Alamat | https://<domain-penjual>/api/order_status.php |
| Method | GET (baca), PATCH (ubah status) |
| Auth | Header X-API-Key: adk_… |
| Scope baca | orders:read (+ customers:read bila butuh data pelanggan) |
| Scope ubah | orders.status:write |
| Status yang boleh ditulis | pending, closing — tidak ada yang lain |
| Batas laju | 120 permintaan per menit per kunci |
| Idempotensi | Header Idempotency-Key (opsional, sangat disarankan untuk PATCH) |
| Content-Type | application/json; charset=utf-8 |
#Cara penjual menerbitkan kunci
Penjual masuk ke dashboard AdForm → Pengaturan → Developer → API Keys → Buat API key:
- Nama konektor — isi nama sistem yang akan memakainya, misalnya
CRM Closing. Nama ini ikut terkirim ke webhook sebagai penanda siapa yang mengubah status, jadi pakai nama yang mudah dikenali penjual sendiri. Harus berbeda dari key lain yang masih aktif di toko itu — kalau sudah dipakai, penjual menerima penolakan409dan diminta memilih nama lain. - Kegunaan — pilih Ubah status pesanan. Kalau sistem Anda juga perlu nama, nomor, atau alamat pembeli, penjual memilih Ubah status pesanan + baca data pelanggan.
- Kedaluwarsa — pilih tanggal secara sengaja.
Nilai kunci tampil satu kali setelah dibuat. Penjual menyalinnya lalu memasukkannya ke sistem Anda sendiri.
Penjual bisa mencabut kunci kapan saja dari halaman yang sama. Kolom Terakhir dipakai membantunya memastikan sebuah kunci sudah tidak terpakai sebelum dicabut.
#Izin yang diberikan pilihan "Ubah status pesanan"
Kunci dengan kegunaan ini mendapat tepat dua izin: orders:read dan orders.status:write.
Yang bisa dilakukan:
- membaca satu pesanan berdasarkan nomornya;
- memindahkan status pesanan antara
pendingdanclosing.
Yang tidak bisa dilakukan:
- membaca nama, nomor WhatsApp, surel, atau alamat pembeli — itu izin terpisah, lihat di bawah;
- menandai pesanan lunas, dikirim, selesai, dibatalkan, atau nomor tidak valid;
- mengubah harga, jumlah, alamat, atau data pelanggan;
- menghapus pesanan;
- membuat pesanan baru;
- membaca daftar pesanan, produk, atau anggota tim.
#Data pelanggan adalah izin terpisah
orders:read berarti boleh membaca pesanan — produk, jumlah, total, ongkir, kurir, resi, status, waktu dibuat. Blok customer (nama, nomor, surel, alamat lengkap) hanya ikut kalau kunci juga memegang customers:read.
Kalau Anda hanya menyelaraskan status, Anda tidak perlu izin itu. Rekonsiliasi berjalan penuh tanpa satu pun kolom pribadi, dan penjual tidak perlu menyerahkan data pembelinya untuk sesuatu yang tidak memerlukannya.
Jalur tulis tidak pernah mengirim data pelanggan, bahkan untuk kunci yang memegang customers:read. Yang perlu membaca pesanan memakai GET.
orders.status:writesengaja terpisah dariorders:write. Kunci lama yang sudah terbit denganorders:writetidak ikut mendapat kemampuan ubah-status saat endpoint ini dipasang.
#Arti status di AdForm — baca ini sebelum menulis kode
AdForm punya satu kolom status. Ia mencampur status pembayaran dan tahap penanganan dalam satu nilai. Tidak ada kolom terpisah untuk "sudah dibayar" dan "sudah dikirim".
#paid bukan tanda pembayaran berhasil
Ini jebakan yang paling mahal, jadi kami tulis dengan angkanya.
Ketika pembayaran otomatis lewat payment gateway berhasil, AdForm menulis status closing, bukan paid. Status paid hanya pernah ditulis kalau penjual menandainya sendiri dari dashboard.
Sebaran nyata seluruh pesanan di AdForm, diukur 27 Agustus 2026 dari 83 basis data toko, 57.323 baris:
| Status | Jumlah baris | Porsi |
|---|---|---|
pending | 40.945 | 71,43% |
closing | 13.597 | 23,72% |
invalid_no_hp | 1.320 | 2,30% |
completed | 677 | 1,18% |
paid | 398 | 0,69% |
shipped | 208 | 0,36% |
cancelled | 178 | 0,31% |
paid adalah 398 baris dari 57.323. Sistem yang menunggu paid sebagai tanda pembayaran masuk akan menunggu sesuatu yang praktis tidak pernah terjadi.
#Sisi baiknya
95,15% pesanan berada di pending atau closing — dua status yang endpoint ini izinkan Anda tulis. Batasan yang terlihat sempit itu mencakup hampir seluruh pesanan yang benar-benar ada.
#Tujuh nilai yang mungkin
pending, closing, invalid_no_hp, paid, cancelled, shipped, completed.
Anda boleh menulis dua: pending dan closing. Anda akan membaca ketujuhnya lewat GET. Perlakukan lima sisanya sebagai keadaan yang hanya bisa diubah penjual dari dashboard-nya.
#GET — baca satu pesanan
GET /api/order_status.php?id=1287
X-API-Key: adk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx{
"success": true,
"order": {
"id": 1287,
"status": "pending",
"product": "Kopi Gayo 250g",
"variant": "Bubuk",
"quantity": 2,
"total": 213000,
"shipping_fee": 15000,
"payment_method": "cod",
"courier": "JNE",
"resi": null,
"_catatan": "blok customer di bawah HANYA ikut kalau kunci memegang customers:read",
"customer": {
"name": "Rina",
"phone": "628111000111",
"email": null,
"address": "Jl. Merdeka No. 10 RT 02 RW 05",
"village": "Dago",
"district": "Coblong",
"city": "Bandung",
"province": "Jawa Barat",
"postal_code": "40135"
},
"created_at": "2026-08-26T09:14:00+07:00"
}
}Daftar field di atas tetap. Kolom baru yang ditambahkan AdForm di kemudian hari tidak otomatis ikut terkirim.
Field _catatan di contoh itu bukan bagian jawaban sungguhan — hanya penanda di dokumen ini. Jawaban juga membawa request_id di tingkat teratas; sebutkan nilai itu kalau Anda melaporkan masalah.
#PATCH — ubah status
PATCH /api/order_status.php
X-API-Key: adk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Idempotency-Key: spc-1287-closing-01
Content-Type: application/json
{
"id": 1287,
"status": "closing",
"expected_previous_status": "pending",
"external_reference": "SPC-8871"
}| Field | Wajib | Keterangan |
|---|---|---|
id | ya | Nomor pesanan AdForm |
status | ya | pending atau closing |
expected_previous_status | tidak | Status yang Anda kira sedang berlaku. Kalau tidak cocok, permintaan ditolak 409 dan tidak ada yang berubah |
external_reference | tidak | Nomor rujukan di sistem Anda, maksimal 120 karakter. Diteruskan ke webhook |
Jawaban berhasil:
{
"order_id": 1287,
"previous_status": "pending",
"status": "closing",
"updated_at": "2026-08-27T14:31:00+07:00",
"request_id": "9f2c41ab",
"changed": true
}Keberhasilan ditandai kode HTTP 200. Jawaban ini tidak membawa data pelanggan — Anda menyatakan tidak memerlukannya pada jalur tulis, dan kami tidak mengirimkannya.
| Field | Arti |
|---|---|
order_id | Nomor pesanan yang diubah |
previous_status | Status sebelum permintaan ini |
status | Status sesudahnya |
updated_at | Waktu perubahan diterapkan (ISO 8601, zona server). null kalau changed bernilai false — tidak ada perubahan berarti tidak ada waktu perubahan |
request_id | Penanda permintaan, 8 karakter heksadesimal. Sebutkan saat melapor masalah |
changed | false berarti pesanan sudah berada di status yang Anda minta. Itu keberhasilan, bukan kegagalan |
Tidak semua jawaban gagal membawa request_id. Yang berasal dari endpoint ini membawanya: 400, 404, 405, 409, 422, 500. Yang berasal dari lapisan bersama di depannya tidak, dan hanya memuat field error:
| Status | Ditangani oleh | Bentuk badan |
|---|---|---|
401, 403 | pemeriksa kunci API bersama | { "error": "..." } |
429 | pembatas laju bersama | { "error": "..." } |
Lapisan itu melayani seluruh endpoint ber-kunci-API, jadi bentuk jawabannya sengaja tidak diubah demi satu endpoint. Untuk kedua kelompok status tersebut, cabangkan program Anda pada status HTTP, bukan pada keberadaan code atau request_id.
#expected_previous_status — kapan dipakai
Kirimkan kalau keputusan Anda didasarkan pada status yang Anda baca sebelumnya.
Contoh: sistem Anda membaca pesanan pukul 10.00 (status pending), lalu CS menekan tombol closing pukul 10.05. Di antara dua waktu itu penjual mungkin sudah menandai pesanan tersebut lunas dari dashboard. Dengan expected_previous_status: "pending", permintaan Anda ditolak alih-alih menimpa keputusan yang lebih baru.
#Idempotensi
Kirim header Idempotency-Key pada setiap PATCH. Isinya bebas, 8–120 karakter dari A-Z a-z 0-9 . _ : -, dan harus unik per permintaan — misalnya spc-<nomor-pesanan>-<status>-<percobaan>.
Kalau permintaan Anda time-out dan Anda tidak tahu apakah ia sampai, kirim ulang dengan kunci dan isi yang sama persis. AdForm mengembalikan jawaban aslinya tanpa mengubah pesanan lagi, disertai header:
Idempotency-Replayed: trueYang diputar ulang adalah jawaban aslinya, jadi request_id dan updated_at yang kembali adalah milik permintaan pertama. Itu memang yang benar: perubahannya terjadi saat itu, bukan saat kiriman ulang tiba.
Aturannya:
- Kunci sama + isi sama → jawaban asli diputar ulang. Pesanan tidak tersentuh.
- Kunci sama + isi berbeda → ditolak
422 idempotency_key_reused. Ini melindungi Anda: kunci yang tidak sengaja dipakai ulang untuk pesanan lain akan ketahuan, bukan diam-diam mengembalikan hasil pesanan pertama. - Kunci sama, permintaan pertama masih berjalan →
409 request_in_progress. Tunggu beberapa detik lalu coba lagi.
Yang diingat hanyalah keberhasilan. Setiap penolakan melepaskan catatannya, jadi kiriman ulang dengan kunci yang sama dinilai ulang dari awal. Contoh: permintaan Anda ditolak 422 closing_incomplete karena alamat pesanan belum lengkap; penjual melengkapinya dari dashboard; Anda kirim ulang permintaan yang sama persis dan kali ini berhasil. Kalau penolakan ikut diingat, Anda akan menerima penolakan lama itu selama 30 hari meski datanya sudah benar.
Urutan penulisan kunci JSON tidak berpengaruh: {"id":1,"status":"closing"} dan {"status":"closing","id":1} dianggap permintaan yang sama.
Masa simpan catatan idempotensi: 30 hari sejak permintaan pertama. Setelah itu catatannya dihapus, dan kiriman ulang dengan kunci yang sama akan dijalankan sebagai permintaan baru. Ini disengaja — tidak ada pemanggil yang mengulang permintaan sebulan kemudian, dan menyimpannya selamanya berarti tabel yang tumbuh tanpa batas.
#Kode error
Semua kegagalan menjawab JSON dengan field error (kalimat untuk manusia) dan code (penanda tetap untuk program Anda).
| HTTP | code | Arti | Yang harus Anda lakukan |
|---|---|---|---|
| 400 | invalid_json | Body bukan JSON objek | Perbaiki permintaan |
| 400 | invalid_request | id atau status kosong / bukan angka positif | Perbaiki permintaan |
| 401 | — | Kunci tidak ada, salah bentuk, dicabut, atau kedaluwarsa | Minta penjual menerbitkan kunci baru |
| 403 | — | Kunci sah tetapi tidak punya scope yang dibutuhkan | Minta penjual membuat kunci dengan kegunaan Ubah status pesanan |
| 404 | order_not_found | Pesanan tidak ada di akun penjual ini | Jangan ulangi |
| 405 | method_not_allowed | Method selain GET/PATCH | Perbaiki permintaan |
| 409 | status_conflict | expected_previous_status tidak cocok dengan status sekarang | Baca ulang pesanannya, putuskan lagi |
| 409 | order_terminal | Status sekarang sudah final (paid, shipped, completed, cancelled, invalid_no_hp) | Jangan ulangi. Status final milik penjual |
| 409 | request_in_progress | Permintaan dengan kunci idempotensi sama sedang berjalan | Tunggu 5 detik, coba lagi |
| 409 | update_conflict | Status berubah tepat saat diproses | Ulangi permintaan |
| 422 | status_not_allowed | status di luar pending/closing | Jangan ulangi |
| 422 | expected_status_not_allowed | expected_previous_status di luar pending/closing. Nilai itu tidak akan pernah cocok, jadi ditolak sekarang alih-alih jadi 409 yang membingungkan | Jangan ulangi |
| 422 | external_reference_too_long | external_reference lebih dari 120 karakter | Perpendek, lalu ulangi |
| 422 | closing_incomplete | Penjual memakai integrasi AdStack dan data pesanan belum lengkap. Field yang kurang ada di missing_fields | Penjual harus melengkapi data dari dashboard |
| 422 | idempotency_key_invalid | Bentuk Idempotency-Key tidak sah | Perbaiki permintaan |
| 422 | idempotency_key_reused | Kunci idempotensi dipakai untuk isi berbeda | Pakai kunci baru |
| 429 | — | Melewati 120 permintaan per menit | Tunggu sesuai header Retry-After |
| 500 | internal_error | Kegagalan di sisi AdForm | Coba lagi dengan kunci idempotensi yang sama |
#Jejak di dashboard penjual
Setiap perubahan lewat endpoint ini tercatat di riwayat edit pesanan dengan sumber api dan nama kunci sebagai pelakunya. Penjual bisa melihat bahwa statusnya dipindahkan oleh integrasi, dan integrasi yang mana.
#Kaitan dengan webhook
Perubahan status lewat endpoint ini memicu event order.status_changed seperti perubahan dari dashboard. Payload-nya membawa tiga field tambahan:
{
"changed_by": "integration",
"integration": "CRM Closing",
"external_reference": "SPC-8871"
}changed_by bernilai integration di sini. Field ini tidak muncul pada perubahan dari dashboard, sehingga penerima webhook yang sudah berjalan tidak perlu menyesuaikan apa pun. Gunanya: membedakan perubahan yang berasal dari sistem Anda sendiri, supaya dua sistem tidak saling memantulkan status.
Selengkapnya di Webhook Keluar.
#Jangan jadikan webhook satu-satunya sumber kebenaran
Ketiga jalur yang menulis status di AdForm — penjual dari dashboard, konfirmasi pembayaran otomatis, dan endpoint ini — mengantre order.status_changed di dalam transaksi yang sama dengan perubahannya, jadi tidak ada perubahan status yang lolos tanpa event. Tetapi pengantreannya digerbangi kuota kiriman per toko: 3.000 kiriman per jam per toko (WEBHOOK_OUT_HOURLY_CAP).
Kalau kuota itu penuh, perubahan statusnya tetap tersimpan sementara kirimannya tidak pernah masuk antrean. Tidak ada galat, dan tidak ada percobaan ulang — kiriman itu memang tidak pernah dibuat.
Karena itu: pakai webhook sebagai pemberitahuan cepat, dan GET sebagai rekonsiliasi. Toko yang ramai adalah justru toko yang paling mungkin menyentuh kuota, dan juga yang paling merugikan kalau statusnya menyimpang tanpa ketahuan.
#Keamanan
- Kunci API adalah kredensial. Simpan di penyimpanan rahasia sistem Anda, bukan di kode sumber atau catatan.
- Jangan pernah meminta atau mengirim kunci lewat WhatsApp, email, atau chat. Penjual menerbitkannya sendiri dari dashboard dan memasukkannya sendiri ke sistem Anda.
- Kalau kunci diduga bocor, penjual mencabutnya dari dashboard dan menerbitkan yang baru. Pencabutan berlaku seketika.