Desain API agent browser yang aman perlu mengatasi masalah yang jarang muncul pada integrasi manual: aksi bisa terkirim dua kali, callback bisa terlambat, urutan event bisa berubah, dan kredensial tool bisa bocor. Jika API Anda dipakai oleh agent browser, automation runner, atau server tooling yang menjalankan aksi atas nama pengguna, maka kontrak API harus tahan terhadap retry, aman untuk akses terlingkup, dan mudah diaudit.

Artikel ini membahas pola praktis untuk merancang API yang stabil dan aman, terinspirasi oleh ekosistem developer yang makin banyak memakai agent browser dan tooling otomatis. Fokusnya bukan pada berita atau produk tertentu, tetapi pada keputusan desain backend: scoped auth, token per tool, idempotency key, verifikasi webhook, retry dengan backoff, deduplikasi event, timeout, dan audit log.

Mengapa API untuk agent browser berbeda dari integrasi biasa

Pada integrasi klasik, klien biasanya berupa aplikasi frontend atau server internal yang perilakunya relatif dapat diprediksi. Pada agent browser, ada lapisan orkestrasi tambahan: tool pemanggil, runtime agent, antrean pekerjaan, browser automation, dan callback asinkron. Akibatnya, API harus siap menghadapi kondisi berikut:

  • Double-submit: aksi yang sama dikirim dua kali karena retry jaringan atau bug orchestration.
  • Callback terlambat: hasil pekerjaan datang setelah klien sudah timeout atau menganggap job gagal.
  • Urutan event tidak konsisten: event completed bisa tiba sebelum started karena jalur pemrosesan berbeda.
  • Secret bocor: token tool tersimpan di log, prompt, atau sistem pihak ketiga.
  • Delivery at-least-once: webhook dapat dikirim lebih dari sekali dan tetap harus aman diproses.

Karena itu, desain API sebaiknya menganggap bahwa request, event, dan callback tidak selalu unik, tidak selalu berurutan, dan tidak selalu tepat waktu.

Prinsip kontrak API yang stabil

1. Pisahkan resource sinkron dan job asinkron

Untuk aksi browser atau tooling yang bisa memakan waktu lama, hindari endpoint yang memblokir sampai semua proses selesai. Lebih aman memakai pola job:

  • POST /v1/browser-actions untuk membuat job
  • GET /v1/browser-actions/{id} untuk mengambil status job
  • Webhook untuk notifikasi perubahan status

Pola ini bekerja karena timeout HTTP klien tidak memengaruhi pemrosesan backend. Jika klien putus koneksi, job tetap bisa berjalan dan hasilnya dapat diambil ulang.

2. Gunakan status yang eksplisit dan terminal yang jelas

Jangan hanya memakai success dan failed. Gunakan state machine sederhana yang mudah diaudit, misalnya:

  • accepted: request diterima
  • queued: menunggu worker
  • running: sedang diproses
  • succeeded: selesai sukses
  • failed: selesai gagal
  • canceled: dibatalkan
  • expired: tidak valid lagi karena TTL terlewati

State terminal harus jelas. Jika job sudah succeeded, webhook ulang dengan payload lama tidak boleh mengubah state menjadi running.

3. Versikan API tanpa mematahkan klien

Letakkan versi di path seperti /v1 atau lewat header versi yang konsisten. Tambahkan field baru dengan cara kompatibel ke belakang. Hindari mengubah makna field lama. Untuk agent browser, kestabilan kontrak lebih penting daripada desain payload yang terlalu “rapi”.

Scoped auth dan token per tool

Mengapa token global berbahaya

Kesalahan umum adalah memberikan satu API key penuh ke semua tool automation. Jika satu tool bocor atau salah konfigurasi, seluruh akun terdampak. Untuk integrasi agent browser, lebih aman memakai token per tool dengan scope minimum.

Desain scope yang praktis

Scope sebaiknya mengikuti aksi bisnis, bukan hanya role umum. Contoh:

  • browser_actions:create
  • browser_actions:read
  • webhooks:manage
  • sessions:read

Jika tool hanya perlu membuat job dan membaca statusnya, jangan berikan hak mengelola webhook atau membaca semua data akun.

Contoh header autentikasi

POST /v1/browser-actions HTTP/1.1
Host: api.example.com
Authorization: Bearer tkn_tool_abc123
Content-Type: application/json
Idempotency-Key: 8a0d0ec6-4ce5-4a24-9f91-8d4e2f0d89d8
X-Request-Id: req_01J... 

Praktik yang disarankan:

  • Setiap token punya owner, tool_id, dan scope yang tercatat.
  • Simpan token dalam bentuk hash, bukan plaintext, jika sistem Anda menerbitkannya sendiri.
  • Dukung rotasi token tanpa downtime.
  • Berikan metadata seperti nama tool, tanggal pembuatan, dan last used untuk audit.

Jika secret bocor

Anda harus menganggap kebocoran token sebagai kejadian operasional yang akan terjadi. Mitigasinya:

  • Token dibatasi scope dan akun yang spesifik.
  • Token dapat dicabut cepat tanpa memengaruhi tool lain.
  • Audit log menunjukkan token mana yang dipakai untuk request tertentu.
  • Rate limit dan anomali penggunaan dapat mendeteksi penyalahgunaan.

Jangan kirim token ke webhook target, jangan menaruh token di query string, dan jangan mencetak header Authorization ke log aplikasi.

Idempotency untuk aksi berulang dan double-submit

Kapan idempotency wajib

Jika endpoint memicu efek samping, seperti membuat job browser, mengisi form, mengirim data, atau memulai sesi automation, maka idempotency key wajib dipertimbangkan. Ini melindungi sistem dari retry klien, timeout jaringan, dan klik ganda.

Kontrak yang aman

Klien mengirim header Idempotency-Key yang unik untuk satu niat aksi. Server menyimpan kombinasi berikut:

  • idempotency key
  • identitas pemanggil atau token
  • fingerprint request, misalnya hash body yang dinormalisasi
  • hasil respons pertama
  • waktu kedaluwarsa key

Jika request yang sama datang lagi dengan key yang sama dan body identik, server mengembalikan respons sebelumnya. Jika key sama tetapi body berbeda, balas dengan error konflik karena ada indikasi bug klien atau penyalahgunaan.

Contoh request membuat job

POST /v1/browser-actions
Authorization: Bearer tkn_tool_abc123
Content-Type: application/json
Idempotency-Key: 8a0d0ec6-4ce5-4a24-9f91-8d4e2f0d89d8

{
  "action_type": "open_page_and_extract",
  "target_url": "https://example.org/dashboard",
  "callback_url": "https://tool-client.example/webhooks/browser-actions",
  "timeout_seconds": 45,
  "input": {
    "selector": "#report-table"
  }
}

Contoh respons sukses awal

HTTP/1.1 202 Accepted
Content-Type: application/json
Location: /v1/browser-actions/act_123

{
  "id": "act_123",
  "status": "accepted",
  "created_at": "2026-07-04T10:00:00Z",
  "idempotency_key": "8a0d0ec6-4ce5-4a24-9f91-8d4e2f0d89d8"
}

Contoh jika key sama tetapi payload berbeda

HTTP/1.1 409 Conflict
Content-Type: application/json

{
  "error": {
    "code": "idempotency_key_reused_with_different_payload",
    "message": "Idempotency-Key sudah pernah dipakai untuk payload yang berbeda"
  }
}

Mengapa ini penting: tanpa idempotency, retry otomatis dari client library atau API gateway dapat membuat dua job identik yang sama-sama berjalan, lalu memicu dua webhook dan dua perubahan data downstream.

Webhook yang terverifikasi dan tahan duplikasi

Desain webhook yang aman

Webhook tidak boleh dianggap tepercaya hanya karena datang dari internet ke endpoint tertentu. Setidaknya, lakukan:

  • HTTPS wajib
  • Signature verification dengan secret per endpoint atau per subscriber
  • Timestamp validation untuk mencegah replay
  • Event ID unik untuk deduplikasi
  • Fast acknowledgment: kembalikan 2xx cepat, proses berat dilakukan async

Contoh header webhook

POST /webhooks/browser-actions HTTP/1.1
Host: tool-client.example
Content-Type: application/json
X-Webhook-Id: wh_01JABC...
X-Webhook-Timestamp: 1762221600
X-Webhook-Signature: v1=5f2c0d...
X-Event-Type: browser_action.succeeded

Contoh payload webhook

{
  "event_id": "evt_01JABC...",
  "event_type": "browser_action.succeeded",
  "created_at": "2026-07-04T10:00:35Z",
  "data": {
    "action_id": "act_123",
    "status": "succeeded",
    "result": {
      "title": "Dashboard",
      "records_found": 12
    }
  }
}

Verifikasi signature

Pola umum yang aman adalah menghitung HMAC dari string yang mencakup timestamp dan body mentah. Contoh bentuk input:

signed_payload = timestamp + "." + raw_body
signature = HMAC_SHA256(webhook_secret, signed_payload)

Penerima webhook membandingkan signature header dengan hasil HMAC lokal memakai perbandingan konstan waktu, lalu memastikan timestamp masih dalam jendela replay yang diterima.

Mengapa body mentah penting: jika Anda mem-parse JSON lalu meng-serialize ulang, urutan field atau whitespace bisa berubah dan verifikasi gagal.

Deduplikasi event

Webhook hampir selalu harus diasumsikan at-least-once delivery. Simpan event_id yang sudah diproses. Jika event yang sama datang lagi, balas 200 OK atau 204 No Content tanpa memproses efek samping kedua kalinya.

Strategi deduplikasi minimal:

  • Primary key atau unique index pada event_id
  • Status pemrosesan event: received, processing, processed, failed
  • Retry internal jika handler gagal setelah event diterima

Urutan event tidak konsisten

Jangan menulis handler yang bergantung pada urutan mutlak. Lebih aman jika setiap event diproses sebagai “sumber kebenaran lokal” yang memajukan state hanya jika transisinya valid. Contoh:

  • Jika status sekarang succeeded, abaikan event running yang datang terlambat.
  • Jika event completed datang sebelum started, tetap terima jika transisinya terminal dan event memiliki timestamp atau sequence yang masuk akal.

Jika perlu, sertakan salah satu dari:

  • sequence monotonik per action
  • occurred_at yang akurat
  • versi state resource saat event dibuat

Namun, sequence saja tidak cukup bila ada retry lama yang baru terkirim kemudian. Handler tetap harus idempotent.

Retry dengan backoff, timeout, dan status code

Kapan boleh retry

Retry sebaiknya dilakukan untuk error sementara, bukan untuk semua kegagalan. Aturan praktis:

  • Retry: timeout, koneksi putus, 429, 502, 503, 504
  • Jangan retry otomatis: 400, 401, 403, 404, 422 kecuali ada alasan bisnis yang jelas

Backoff yang aman

Gunakan exponential backoff dengan jitter agar sistem tidak menciptakan lonjakan serentak setelah gangguan. Contoh urutan sederhana:

  • percobaan 1: segera
  • percobaan 2: ~1 detik
  • percobaan 3: ~2 detik
  • percobaan 4: ~4 detik
  • tambahkan jitter acak kecil pada tiap langkah

Untuk webhook, sertakan batas retry dan kebijakan dead-letter atau penandaan gagal permanen jika endpoint subscriber terus bermasalah.

Timeout

Pisahkan beberapa jenis timeout:

  • Request timeout di sisi API gateway atau klien
  • Job timeout untuk lama eksekusi browser action
  • Webhook delivery timeout saat menghubungi subscriber

Kesalahan umum adalah menyamakan timeout HTTP 10 detik dengan job timeout 10 detik. Untuk kerja asinkron, request boleh selesai cepat dengan 202 Accepted, sedangkan job berjalan lebih lama sesuai SLA internal.

Status code yang disarankan

  • 202 Accepted: job diterima untuk diproses
  • 200 OK: status job berhasil diambil
  • 201 Created: resource sinkron berhasil dibuat
  • 204 No Content: webhook diterima tanpa body respons
  • 400 Bad Request: payload tidak valid secara sintaks
  • 401 Unauthorized: token tidak valid atau tidak ada
  • 403 Forbidden: token valid tapi scope kurang
  • 409 Conflict: konflik idempotency atau transisi state tidak valid
  • 422 Unprocessable Entity: payload valid secara sintaks, tapi melanggar aturan bisnis
  • 429 Too Many Requests: rate limit terlampaui
  • 500/502/503/504: kegagalan sementara yang bisa memicu retry

Contoh desain endpoint yang praktis

Membuat browser action

POST /v1/browser-actions

Header:

  • Authorization: Bearer ...
  • Idempotency-Key: ...
  • X-Request-Id: ... opsional tapi berguna untuk tracing

Body:

{
  "action_type": "open_page_and_extract",
  "target_url": "https://example.org/dashboard",
  "callback_url": "https://tool-client.example/webhooks/browser-actions",
  "timeout_seconds": 45,
  "input": {
    "selector": "#report-table"
  },
  "metadata": {
    "tool_run_id": "run_789"
  }
}

Mengambil status action

GET /v1/browser-actions/act_123

Contoh respons:

{
  "id": "act_123",
  "status": "running",
  "action_type": "open_page_and_extract",
  "created_at": "2026-07-04T10:00:00Z",
  "updated_at": "2026-07-04T10:00:10Z",
  "expires_at": "2026-07-04T10:05:00Z",
  "last_error": null
}

Mendaftarkan endpoint webhook subscriber

POST /v1/webhook-endpoints

Body:

{
  "url": "https://tool-client.example/webhooks/browser-actions",
  "event_types": [
    "browser_action.accepted",
    "browser_action.running",
    "browser_action.succeeded",
    "browser_action.failed"
  ]
}

Contoh respons:

{
  "id": "whsub_123",
  "url": "https://tool-client.example/webhooks/browser-actions",
  "status": "active",
  "secret_last4": "9f3a"
}

Secret webhook sebaiknya hanya ditampilkan sekali saat pembuatan atau rotasi.

Audit log dan observability

Apa yang perlu dicatat

Untuk sistem automation, audit log bukan tambahan opsional. Minimal catat:

  • siapa atau token mana yang membuat job
  • scope yang dipakai
  • idempotency key
  • request id dan trace id
  • perubahan status job
  • upaya pengiriman webhook dan hasilnya
  • rotasi atau pencabutan secret

Audit log berbeda dari application log biasa. Audit log harus fokus pada jejak tindakan yang bisa ditelusuri kembali untuk keamanan dan forensik.

Jangan log data sensitif secara mentah

Masking diperlukan untuk:

  • header Authorization
  • secret webhook
  • cookie atau session token
  • payload yang mengandung kredensial atau data pribadi

Jika perlu menyimpan payload untuk debugging, pertimbangkan redaksi field sensitif sebelum disimpan.

Edge case yang sering merusak integrasi

1. Double-submit dari klien

Penyebabnya bisa timeout, retry library, atau pengguna menekan aksi berulang. Solusi utamanya adalah idempotency key dan penyimpanan hasil request pertama.

2. Callback terlambat setelah klien menganggap gagal

Klien mungkin menandai job gagal karena timeout lokal, padahal backend masih memproses dan akhirnya sukses. Solusinya:

  • anggap webhook dan polling sebagai sumber kebenaran akhir
  • jangan langsung menghapus konteks job hanya karena timeout request awal
  • berikan TTL job dan status terminal yang eksplisit

3. Event datang tidak berurutan

Handler harus berbasis transisi state yang valid, bukan asumsi urutan kedatangan. Simpan timestamp atau sequence, tetapi tetap buat handler idempotent.

4. Secret bocor

Jika token atau webhook secret bocor:

  • cabut atau rotasi secret segera
  • cek audit log untuk pola request mencurigakan
  • pastikan scope sempit agar dampak terbatas
  • berikan mekanisme re-issue tanpa mengubah semua integrasi lain

5. Worker selesai, tetapi webhook gagal dikirim

Jangan gabungkan keberhasilan job dengan keberhasilan delivery webhook. Job bisa succeeded walau webhook ke subscriber gagal sementara. Simpan status delivery terpisah dan retry sesuai kebijakan.

Contoh pseudocode backend

Validasi idempotency saat create action

function createBrowserAction(request, token) {
  requireScope(token, "browser_actions:create")

  validate(request.body)
  key = request.headers["Idempotency-Key"]
  if (!key) return error(400, "missing_idempotency_key")

  fingerprint = hashCanonicalJson(request.body)
  existing = idempotencyStore.find(token.id, key)

  if (existing) {
    if (existing.fingerprint != fingerprint) {
      return error(409, "idempotency_key_reused_with_different_payload")
    }
    return existing.response
  }

  action = insertAction({
    status: "accepted",
    requested_by_token_id: token.id,
    payload: request.body
  })

  response = {
    id: action.id,
    status: action.status
  }

  idempotencyStore.save(token.id, key, fingerprint, response)
  enqueue(action.id)
  return accepted(response)
}

Verifikasi webhook masuk

function handleWebhook(rawBody, headers, secret) {
  timestamp = headers["X-Webhook-Timestamp"]
  signature = headers["X-Webhook-Signature"]

  if (isExpired(timestamp)) {
    return error(400, "stale_webhook")
  }

  expected = hmacSha256(secret, timestamp + "." + rawBody)
  if (!constantTimeEquals(signature, "v1=" + expected)) {
    return error(401, "invalid_signature")
  }

  event = parseJson(rawBody)
  if (eventStore.alreadyProcessed(event.event_id)) {
    return noContent()
  }

  eventStore.markReceived(event.event_id)
  enqueueWebhookHandling(event)
  return noContent()
}

Checklist implementasi backend

  1. Tentukan model resource asinkron dengan status terminal yang jelas.
  2. Versikan API dan hindari perubahan payload yang mematahkan klien.
  3. Terapkan token per tool, scope minimum, dan mekanisme rotasi.
  4. Simpan token dalam bentuk hash jika Anda menerbitkannya sendiri.
  5. Wajibkan Idempotency-Key untuk endpoint yang menimbulkan efek samping.
  6. Simpan fingerprint payload dan respons pertama untuk replay idempotent.
  7. Kembalikan 409 Conflict jika key sama dipakai untuk payload berbeda.
  8. Dukung webhook dengan secret unik, signature HMAC, dan validasi timestamp.
  9. Simpan event_id untuk deduplikasi webhook.
  10. Pastikan handler event idempotent dan tahan urutan event yang tidak konsisten.
  11. Gunakan retry dengan exponential backoff plus jitter untuk error sementara.
  12. Pisahkan timeout request, timeout job, dan timeout delivery webhook.
  13. Catat audit log untuk token, scope, request id, transisi state, dan rotasi secret.
  14. Masking semua secret dan kredensial dari log.
  15. Tambahkan rate limit dan deteksi anomali dasar per token atau tool.
  16. Sediakan endpoint status job agar klien tidak hanya bergantung pada webhook.

Penutup

Desain API agent browser yang aman bukan hanya soal autentikasi, tetapi soal membuat seluruh alur integrasi tahan terhadap kondisi nyata: retry, duplikasi, event terlambat, dan kebocoran secret. Kombinasi kontrak resource asinkron, scoped auth, token per tool, idempotency key, webhook terverifikasi, deduplikasi event, timeout yang tepat, dan audit log akan mengurangi bug operasional yang sulit dilacak.

Jika Anda sedang membangun integrasi untuk agent browser atau tooling otomatis, mulai dari yang paling berdampak: endpoint job yang stabil, idempotency di aksi tulis, dan verifikasi webhook yang benar. Tiga hal ini biasanya menyelesaikan sebagian besar masalah produksi sebelum sistem bertambah kompleks.