Dokumentasi API
Referensi lengkap REST API WA Client 1 — gateway WhatsApp multi-nomor dari PT Surya Digital Nusantara. Satu akun bisa mengelola beberapa nomor WhatsApp sekaligus, masing-masing dengan API key, riwayat chat, dan webhook sendiri yang terisolasi penuh dari nomor/akun lain.
Gambaran Umum
Base URL untuk seluruh endpoint di bawah ini:
https://pesan-client1.suryacloud.my.id
Setiap request dan response menggunakan JSON (content-type: application/json), kecuali endpoint upload media yang menerima multipart/form-data.
Mulai Cepat
- Login dengan email & nomor WhatsApp aktif (verifikasi kode OTP) — akun dibuat otomatis saat pertama kali login.
- Di halaman Nomor Saya, klik + Tambah Nomor dan beri nama.
- Salin API key yang ditampilkan — hanya muncul sekali saat itu juga.
- Buka dashboard nomor tersebut, scan QR dari WhatsApp di HP (Perangkat Tertaut → Tautkan Perangkat).
- Panggil endpoint di bawah menggunakan API key nomor tersebut.
Autentikasi
Endpoint pengiriman pesan (/api/send/*, /api/groups, /api/connection/*, /api/webhooks/*, /media/*) diautentikasi dengan API key per-nomor, dikirim lewat header x-api-key (atau parameter query ?key= jika header tidak memungkinkan, mis. embed <img>).
x-api-key: API_KEY_NOMOR_ANDA
x-api-key di setiap pemanggilan API dari server/skrip Anda. ?key= hanya dimaksudkan untuk kasus embed media (<img>/<video> src) yang tidak bisa mengirim header — nilai di query string berisiko tercatat di access log atau bocor lewat header Referer, jadi jangan pakai bentuk ini di request server-to-server biasa.Login dashboard (halaman web) menggunakan email & nomor WhatsApp aktif (verifikasi kode OTP), terpisah sepenuhnya dari API key — API key tidak pernah digunakan untuk login ke dashboard, dan sebaliknya.
Mengirim pesan teks ke nomor personal atau grup.
| Field | Tipe | Keterangan |
|---|---|---|
to | string | Nomor tujuan (mis. 628123456789) atau JID grup (...@g.us). |
message | string | Isi pesan teks. |
idempotencyKey | string | Opsional. Kirim ID unik milik Anda sendiri (mis. ID transaksi/notifikasi di sistem Anda) — kalau request dengan idempotencyKey yang sama dikirim ulang (misal karena timeout lalu retry), WA Client 1 tidak akan mengirim pesan dua kali, cukup mengembalikan hasil pengiriman yang pertama. Sangat disarankan dipakai untuk sistem otomatis (reminder, notifikasi terjadwal, dsb.) yang mungkin melakukan retry. |
504 (bukan diam tanpa respons). Proses pengiriman tetap berjalan di belakang layar walau responsnya sudah 504. Set timeout client Anda ke minimal 30 detik, dan kalau menerima 504: kirim ulang request yang sama dengan idempotencyKey yang sama persis — Anda tidak akan pernah mengirim pesan kedua, dan tidak perlu menebak apakah pesan pertama terkirim.Saat memeriksa ulang, ada dua kemungkinan jawaban: kalau pengiriman sudah selesai, Anda langsung mendapat hasil akhirnya (
200 berisi messageId, atau error yang sebenarnya). Kalau masih diproses, Anda mendapat 504 lagi tapi seketika — bukan setelah menunggu 20 detik lagi — beserta header retry-after yang menunjukkan kapan sebaiknya diperiksa lagi. Jadi memeriksa berulang kali aman dan tidak membuat sistem Anda menggantung.curl -X POST https://pesan-client1.suryacloud.my.id/api/send/text \ -H "x-api-key: API_KEY_ANDA" \ -H "content-type: application/json" \ -d '{"to":"628123456789","message":"Halo dari WA Client 1","idempotencyKey":"reminder-2026-08-16-ainun"}'
const res = await fetch('https://pesan-client1.suryacloud.my.id/api/send/text', { method: 'POST', headers: { 'x-api-key': 'API_KEY_ANDA', 'content-type': 'application/json' }, body: JSON.stringify({ to: '628123456789', message: 'Halo dari WA Client 1' }), });
import requests requests.post( 'https://pesan-client1.suryacloud.my.id/api/send/text', headers={'x-api-key': 'API_KEY_ANDA'}, json={'to': '628123456789', 'message': 'Halo dari WA Client 1'}, )
Response 200
{ "success": true, "messageId": "3EB0..." }Mengirim gambar, video, dokumen, atau audio. Terima file langsung (multipart/form-data) atau URL media yang sudah publik.
| Field | Tipe | Keterangan |
|---|---|---|
to | string | Nomor atau JID grup tujuan. |
file | file | Upload langsung (opsional jika mediaUrl diisi). |
mediaUrl | string | URL http(s) ke file media (opsional jika file diisi). |
type | string | image / video / document / audio — wajib jika pakai mediaUrl, opsional (auto-deteksi dari mimetype) jika upload file. |
caption | string | Opsional, teks penyerta. |
fileName | string | Opsional, nama file untuk dokumen. |
idempotencyKey | string | Opsional, sama seperti pada /api/send/text di atas — mencegah kiriman ganda kalau request yang sama di-retry. |
/api/send/text — lihat catatan di bagian di atas.curl -X POST https://pesan-client1.suryacloud.my.id/api/send/media \ -H "x-api-key: API_KEY_ANDA" \ -F "to=628123456789" \ -F "file=@/path/ke/gambar.jpg" \ -F "caption=Contoh gambar"
const form = new FormData(); form.append('to', '628123456789'); form.append('mediaUrl', 'https://contoh.com/gambar.jpg'); form.append('type', 'image'); await fetch('https://pesan-client1.suryacloud.my.id/api/send/media', { method: 'POST', headers: { 'x-api-key': 'API_KEY_ANDA' }, body: form, });
Daftar grup WhatsApp yang diikuti nomor ini.
| Field | Tipe | Keterangan |
|---|---|---|
limit | number | Opsional, query param. Jumlah maksimum grup per halaman (default 100, maksimum 500). |
offset | number | Opsional, query param. Lewati sekian grup pertama (default 0) — dipakai bersama limit untuk paginasi. |
{ "groups": [ { "id": "1234-5678@g.us", "subject": "Tim Support", "participants": 12 } ], "total": 34, "limit": 100, "offset": 0 }Status koneksi WhatsApp nomor ini, termasuk QR code kalau sedang menunggu discan. Dipakai untuk membangun halaman "Hubungkan WhatsApp" sendiri di sistem Anda (mis. panel admin sekolah) tanpa perlu membuka dashboard WA Client 1 — panggil endpoint ini setiap ~3 detik selama proses pairing untuk menampilkan QR terbaru secara realtime, sama seperti yang dilakukan dashboard kami sendiri.
data:image/png;base64,... — tinggal dipasang langsung ke <img src>, tidak perlu diolah lagi. Bernilai null kalau tidak sedang menunggu scan (misal sudah open, atau baru connecting).🔑 API key lainnya) harus dipanggil dari backend Anda sendiri, bukan langsung dari halaman admin di browser — API key yang ditaruh di kode sisi client bisa diambil siapa saja yang membuka DevTools. Backend Anda yang memanggil WA Client 1, lalu meneruskan hasilnya (QR + status) ke halaman admin Anda lewat mekanisme Anda sendiri (polling ke endpoint Anda sendiri, misalnya).| Field | Tipe | Keterangan |
|---|---|---|
state | string | connecting / qr / open / close. |
qr | string|null | Data URL QR code, hanya terisi saat state = qr. |
connectedNumber | string|null | Nomor HP yang tersambung, kalau state = open. |
staleRescanNeeded | boolean | true kalau watchdog otomatis kami yang menghentikan koneksi ini (lihat FAQ) — perlu /api/connection/logout lalu scan ulang, bukan sekadar /restart. |
autoRetryGivenUp | boolean | true kalau percobaan sambung-ulang otomatis sudah menyerah — perlu /api/connection/restart manual. |
pairingStopped | boolean | true kalau QR tidak discan selama beberapa kali masa berlaku QR berturut-turut (±14 menit) dan sistem berhenti menampilkan QR baru terus-menerus. Sesi tidak dihapus — cukup panggil /api/connection/restart saat siap scan, dan QR baru akan muncul di /api/connection/status. Status ini bertahan walau server di-restart. Event webhook device.disconnected menyertakan reason: "pairing_timeout" dan needsQrScan: true saat ini terjadi. |
curl https://pesan-client1.suryacloud.my.id/api/connection/status \
-H "x-api-key: API_KEY_ANDA"
const res = await fetch('https://pesan-client1.suryacloud.my.id/api/connection/status', { headers: { 'x-api-key': 'API_KEY_ANDA' }, }); const { state, qr, connectedNumber } = await res.json();
Response 200
{ "state": "qr", "qr": "data:image/png;base64,...", "connectedNumber": null, "lastDisconnectCode": null, "reconnectAttempts": 0, "autoRetryGivenUp": false, "staleRescanNeeded": false, "pairingStopped": false }Sambung ulang pakai sesi yang sama (tidak perlu scan QR) — dipakai kalau koneksi terlihat macet, sama seperti tombol Restart Koneksi di dashboard. Selesai dalam beberapa detik di belakang layar — poll /api/connection/status setelahnya untuk melihat hasilnya, respons endpoint ini sendiri tidak menunggu proses reconnect selesai.
Logout penuh — menghapus sesi tersimpan, sama seperti tombol Logout & Scan Ulang di dashboard. Setelah ini /api/connection/status akan mulai mengembalikan QR baru untuk discan lagi. Gunakan hanya saat benar-benar perlu re-pairing (mis. staleRescanNeeded bernilai true) — jangan dipanggil rutin, karena ini memutus koneksi yang sedang aktif.
{ "success": true }Webhook
Dikelola dari halaman Webhook di dashboard nomor Anda, atau lewat API key (lihat di bawah) — misalnya untuk mendaftarkan webhook otomatis begitu integrasi Anda sendiri berhasil terhubung, tanpa langkah manual di dashboard. Satu nomor bisa punya lebih dari satu tujuan webhook sekaligus — misalnya satu ke Google Apps Script untuk otomasi, satu lagi ke server OTP login — setiap event dikirim ke semua endpoint yang aktif secara paralel dan independen. Tiap endpoint punya URL dan secret HMAC-nya sendiri, jadi merotasi atau menghapus satu integrasi tidak memengaruhi yang lain.
Kelola Webhook Lewat API
| Method | Path | Keterangan |
|---|---|---|
| GET | /api/webhooks | Daftar semua endpoint webhook nomor ini. |
| POST | /api/webhooks | Daftarkan endpoint baru. Body: { "label": "...", "url": "https://..." }. Response 201 berisi secret HMAC — simpan, dipakai untuk verifikasi signature (lihat bagian Event & Signature di bawah). |
| PATCH | /api/webhooks/:id | Ubah label / url / enabled sebagian (kirim hanya field yang mau diubah). |
| POST | /api/webhooks/:id/rotate-secret | Terbitkan secret HMAC baru untuk endpoint ini (yang lama langsung tidak berlaku). |
| POST | /api/webhooks/:id/test | Kirim satu payload uji coba bertanda tangan ke endpoint ini sekarang juga, untuk memastikan verifikasi signature di sisi Anda benar sebelum mengandalkannya untuk event sungguhan. |
| DELETE | /api/webhooks/:id | Hapus endpoint webhook. |
400.curl -X POST https://pesan-client1.suryacloud.my.id/api/webhooks \ -H "x-api-key: API_KEY_ANDA" \ -H "content-type: application/json" \ -d '{"label":"Sistem Internal","url":"https://contoh.com/webhook/whatsapp"}'
Response 201
{ "endpoint": { "id": "...", "label": "Sistem Internal", "url": "https://contoh.com/webhook/whatsapp", "secret": "...", "enabled": true } }Event yang Dikirim
| Event | Kapan Terjadi |
|---|---|
message.incoming | Ada pesan masuk (personal atau grup). |
message.status | Status pesan keluar berubah (sent / delivered / read / failed). |
identity.merged | Kontak yang sebelumnya dikenali lewat @lid berhasil diketahui nomor HP aslinya (lihat penjelasan LID di bawah) — pakai ini untuk menggabungkan kontak/thread lama Anda. |
device.connected | Nomor berhasil terhubung ke WhatsApp. |
device.disconnected | Koneksi terputus (cek field needsQrScan). |
Setiap payload otomatis disisipi numberId dan phoneNumber milik nomor WA Client1 Anda sendiri (bukan lawan bicara) — jadi satu URL webhook bisa dipakai untuk beberapa nomor sekaligus dan tetap bisa dibedakan asalnya. Untuk identitas pengirim/lawan bicara, selalu pakai chatId/from, bukan phoneNumber di level atas ini.
{
"event": "message.incoming",
"chatId": "628123456789@s.whatsapp.net",
"from": "628123456789@s.whatsapp.net",
"isGroup": false,
"isFromMe": false,
"type": "text",
"text": "Halo, ada info?",
"numberId": "b3f1...",
"phoneNumber": "6281234567890"
}chatId/from
pada message.incoming bisa berisi identitas WhatsApp internal berformat
<angka-acak>@lid alih-alih <nomor-hp>@s.whatsapp.net — biasanya
untuk kontak yang belum pernah mengirim lewat nomor HP aslinya dan belum tersimpan di
address book nomor WA Client1 Anda. Ini bukan bug pengiriman: WhatsApp memang tidak selalu
membocorkan nomor HP asli kontak seperti itu di level protokol, bahkan aplikasi WhatsApp
resmi si pengirim pun menampilkannya sebagai kontak terpisah sampai nomornya "ketemu".
WA Client1 otomatis meresolusi
@lid → nomor HP asli begitu nomornya
diketahui (lewat sinkronisasi kontak, atau kontak tsb membagikan nomornya), lalu memakai
nomor asli itu untuk seluruh pesan berikutnya. Kalau resolusi ini terjadi setelah Anda
sudah menerima satu/lebih message.incoming dengan chatId ber-@lid,
Anda akan menerima event identity.merged berisi oldChatId (yang lama, @lid)
dan newChatId (nomor HP asli) — pakai ini untuk menggabungkan kontak/thread yang
sempat tercatat terpisah di sisi Anda. Jangan asumsikan chatId/from selalu
berformat @s.whatsapp.net, dan jangan pakai field phoneNumber di level atas
sebagai pengganti — field itu identitas nomor WA Client1 Anda sendiri, bukan lawan bicara.
Contoh payload message.status — perhatikan nama field-nya chatId, bukan to:
{
"event": "message.status",
"messageId": "3EB0...",
"chatId": "628123456789@s.whatsapp.net",
"status": "delivered",
"timestamp": 1786450000,
"numberId": "b3f1...",
"phoneNumber": "6281234567890"
}Contoh payload identity.merged:
{
"event": "identity.merged",
"oldChatId": "44294803030187@lid",
"newChatId": "628123456789@s.whatsapp.net",
"numberId": "b3f1...",
"phoneNumber": "6281234567890"
}Verifikasi Signature
Setiap request webhook menyertakan header x-webhook-signature berisi HMAC-SHA256 dari body mentah, ditandatangani dengan secret milik endpoint tujuan itu sendiri (beda dari API key, dan beda per endpoint kalau Anda punya lebih dari satu — lihat halaman Webhook di dashboard).
const crypto = require('crypto'); function isValid(rawBody, header, secret) { const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex'); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header)); }
import hmac, hashlib def is_valid(raw_body: bytes, header: str, secret: str) -> bool: expected = 'sha256=' + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, header)
numberId/phoneNumber di body tanpa verifikasi terlebih dahulu.Verifikasi Kepemilikan Nomor WhatsApp
Pola siap-pakai untuk kasus yang sangat umum: sistem Anda punya form pendaftaran dengan kolom nomor WhatsApp, dan Anda perlu membuktikan pengisinya benar-benar menguasai nomor itu — sebelum mengandalkannya untuk kirim info penting (undangan interview, status pesanan, kode akses, dsb). Pola di bawah ini dipakai secara produksi oleh salah satu integrasi kami dan terbukti jauh lebih aman untuk reputasi nomor Anda dibanding OTP biasa.
Kenapa bukan OTP biasa
OTP klasik (sistem Anda kirim kode duluan ke nomor yang baru didaftarkan) terlihat aman, tapi punya risiko tersembunyi kalau volume pendaftaran cukup ramai: nomor WA Client 1 Anda jadi pihak yang memulai kontak ke ribuan nomor baru yang belum pernah membalas apa pun. Pola itu — banyak pesan keluar, nyaris nol balasan — adalah salah satu sinyal terkuat yang dipakai sistem anti-spam WhatsApp. Ini bukan teori: salah satu nomor produksi di gateway ini pernah di-logout paksa oleh WhatsApp (kode 401, perlu scan ulang) setelah mengirim pesan massal ke banyak penerima sekaligus. Integrasi tersebut lalu memilih pola terbalik di bawah ini supaya pola berisiko yang sama tidak berulang setiap kali ada pendaftar baru.
Pola yang direkomendasikan: OTP terbalik
Alih-alih Anda mengirim kode ke pengguna, pengguna yang mengirim kode ke Anda:
- Sistem Anda membuat token acak sekali-pakai per pengguna (contoh:
SBWVERIF-a1b2c3d4e5— prefiks tetap yang mudah Anda kenali lewat regex, plus bagian acak yang cukup panjang untuk tidak bisa ditebak). - Tampilkan tombol/link
wa.meyang sudah terisi otomatis dengan token itu, mengarah ke nomor WA Client 1 Anda sendiri:https://wa.me/<nomor-anda>?text=<token>. Pengguna tinggal tekan Kirim di WhatsApp — tidak perlu mengetik apa pun. - Daftarkan webhook untuk event
message.incoming. Saat pesan masuk cocok pola token Anda, itu bukti pengirimnya menguasai sesi WhatsApp aktif di baliknya. - Tandai token itu terverifikasi, lalu balas pengguna (bukan mengirim duluan) untuk konfirmasi — lihat contoh kode di bawah.
// POST /webhook-anda, setelah signature diverifikasi (lihat bagian Webhook di atas) const POLA_TOKEN = /^PREFIKS-[a-f0-9]{10}$/i; if (payload.event === 'message.incoming' && !payload.isFromMe && !payload.isGroup) { const teks = (payload.text || '').trim(); if (POLA_TOKEN.test(teks)) { const baris = await cariToken(teks.toUpperCase()); if (baris && baris.status === 'pending') { await tandaiTerverifikasi(baris.id, payload.from); // balas via POST /api/send/text ke pengguna, JANGAN ke identitas mentah kalau @lid (lihat di bawah) } } }
// index.php / router Anda, setelah signature diverifikasi $polaToken = '/^PREFIKS-[a-f0-9]{10}$/i'; if ($payload['event'] === 'message.incoming' && empty($payload['isFromMe']) && empty($payload['isGroup'])) { $teks = trim($payload['text'] ?? ''); if (preg_match($polaToken, $teks)) { $baris = $model->cariToken(strtoupper($teks)); if ($baris && $baris['status'] === 'pending') { $model->tandaiTerverifikasi($baris['id'], $payload['from']); // balas via POST /api/send/text — JANGAN ke identitas mentah kalau @lid, lihat di bawah } } }
Nomor pengirim bisa berupa LID — pisahkan "sah" dari "tahu nomornya"
Konsep @lid sudah dijelaskan di bagian Webhook Events — untuk kasus verifikasi kepemilikan nomor ini, konsekuensinya perlu ditangani dengan hati-hati karena dua hal yang terasa mirip sebenarnya berbeda:
- Apakah verifikasinya sah? Ya, selalu — token yang benar dari sesi WhatsApp aktif tetap bukti sah kepemilikan, LID atau bukan. Jangan tolak verifikasi hanya karena identitasnya LID.
- Apakah Anda tahu nomor HP-nya? Belum tentu. Kalau identitasnya
@lid, Anda tidak punya nomor HP asli saat itu juga — jangan coba menebaknya dari angka di depan@lid(itu ID internal acak, bukan nomor telepon, dan mengirim balasan ke situ tidak akan pernah sampai).
WA Client 1 otomatis mencoba resolusi @lid → nomor asli di belakang layar, dan mengirim event identity.merged begitu berhasil — kadang seketika, kadang jam/hari kemudian, kadang tidak pernah (tergantung pengaturan privasi pengguna itu). Manfaatkan event ini: begitu diterima, cocokkan newChatId dengan nomor yang tersimpan di profil pengguna terkait. Kalau cocok, tidak ada yang perlu dilakukan. Kalau berbeda, jangan otomatis mengubah/mencabut status terverifikasi pengguna yang mungkin sudah lanjut memakai sistem Anda — cukup catat untuk ditinjau (log, tiket admin, dsb). Perubahan status secara diam-diam dan asinkron (bisa saja berhari-hari setelah verifikasi asli) lebih berisiko mengganggu pengguna yang sah dibanding manfaat mengejar akurasi sempurna.
Nomor pengirim beda dari yang diisi di form — jangan timpa diam-diam
Ini kasus yang gampang terlewat: token benar, pengirimnya jelas nomor HP asli (bukan LID) — tapi berbeda dari nomor yang pengguna ketik sendiri saat mendaftar. Godaannya adalah langsung menganggap "yang mengirim kode pasti nomor yang benar" dan menimpa data lama. Jangan lakukan itu secara otomatis — pengguna tidak pernah diberi tahu profilnya "berubah", dan kalau tokennya pernah bocor (screenshot yang tidak sengaja dibagikan, dsb), siapa pun bisa mengklaim nomor siapa pun.
Pola yang lebih aman:
- Normalisasi kedua nomor ke format yang sama (kode negara konsisten, tanpa spasi/tanda hubung) sebelum dibandingkan — jangan bandingkan string mentah.
- Kalau sama: lanjutkan seperti biasa, tandai terverifikasi.
- Kalau beda: jangan langsung menimpa atau langsung menolak. Simpan sebagai status antara ("menunggu konfirmasi"), balas ke nomor yang baru saja mengirim (nomor itu baru saja terbukti aktif) dengan penjelasan singkat + arahkan ke halaman web Anda untuk mengonfirmasi secara sadar — tombol "Ya, ini nomor saya" / "Bukan, saya kirim ulang dari nomor terdaftar".
- Hanya setelah pengguna sendiri yang menekan konfirmasi di web, baru data nomor benar-benar diperbarui dan status jadi terverifikasi penuh.
Ringkasan praktik baik
| Jangan | Lakukan sebagai gantinya |
|---|---|
| Kirim OTP duluan ke setiap pendaftar baru secara massal | Pola terbalik: pengguna kirim token ke Anda lewat wa.me, Anda hanya membalas |
Menebak nomor HP dari angka di depan @lid | Terima sebagai verifikasi sah, tapi biarkan kolom nomor apa adanya sampai identitas asli diketahui (lewat identity.merged atau input pengguna sendiri) |
| Menimpa nomor tersimpan begitu saja saat pengirim ≠ nomor form | Tandai "menunggu konfirmasi", minta pengguna mengonfirmasi sadar lewat web sebelum data berubah |
Mencabut status terverifikasi otomatis saat identity.merged datang belakangan dan ternyata beda | Catat untuk ditinjau manual — pengguna itu mungkin sudah lanjut memakai sistem Anda dengan status itu |
Rate Limit
Endpoint /api/send/* dibatasi 20 pesan per menit per nomor (bisa berbeda per deployment) untuk mengurangi risiko deteksi otomatisasi oleh WhatsApp. Response melebihi batas mengembalikan status 429 dengan header retry-after.
Kode Error
| Status | Arti |
|---|---|
400 | Parameter request tidak valid atau kurang. |
401 | API key tidak ada / tidak valid, atau sesi login tidak valid. |
404 | Resource tidak ditemukan (termasuk mengakses nomor yang bukan milik Anda). |
409 | Konflik — mis. batas maksimum jumlah nomor per akun tercapai. |
429 | Melebihi rate limit. |
503 | Sesi WhatsApp nomor ini belum/tidak terhubung saat ini. Coba lagi setelah beberapa saat. |
504 | Pengiriman belum selesai dalam batas waktu respons kami (lihat bagian Kirim Teks) — proses tetap berjalan di server, bukan berarti gagal. |
500 | Kesalahan tak terduga di server. |
{ "error": "pesan penjelasan singkat", "requestId": "3f2a1c9e-..." }x-request-id, dan setiap response error menyertakan requestId yang sama di body. Sertakan nilai ini saat melaporkan masalah ke kami — jauh lebih cepat kami telusuri di log dibanding hanya waktu & deskripsi masalah.429/504 bisa menyertakan header retry-after (dalam detik) yang menunjukkan kapan sebaiknya Anda mencoba lagi.