Bot AI di Slack tidak cukup hanya menerima pesan, memanggil model, lalu mengirim jawaban. Integrasi yang dapat dipercaya harus memverifikasi setiap request, membatasi OAuth scope, mengisolasi data antar-workspace, mengakui event dengan cepat, serta memastikan retry dari Slack tidak menghasilkan respons ganda.
Pemberitaan 404 Media tentang reaksi karyawan Amazon terhadap output AI di Slack dapat dibaca sebagai konteks risiko kepercayaan: ketika jawaban yang buruk, tidak relevan, atau tidak jelas asalnya muncul di ruang kerja, pengguna tidak hanya menilai model—mereka menilai sistem dan organisasi yang memasangnya. Karena itu, kontrol teknis atas input, output, dan kegagalan harus dirancang sejak awal.
Kontrak Slack Events API yang Harus Dipenuhi
Panduan ini berfokus pada Events API melalui endpoint HTTP. Slack mengirim event sebagai request HTTP dan mengharapkan aplikasi memberikan respons sukses dalam waktu singkat. Handler webhook sebaiknya tidak menunggu panggilan model AI, pencarian dokumen, atau pengiriman pesan selesai.
Verifikasi signature sebelum memproses JSON
Slack menandatangani request menggunakan signing secret aplikasi. Server menerima timestamp melalui header X-Slack-Request-Timestamp dan signature melalui X-Slack-Signature. Basis signature dibentuk dari versi, timestamp, dan raw request body:
base_string = 'v0:' + timestamp + ':' + raw_body
expected = 'v0=' + HMAC_SHA256(slack_signing_secret, base_string)Lakukan perbandingan dengan fungsi constant-time. Jangan melakukan serialisasi ulang JSON sebelum verifikasi karena perubahan spasi atau urutan data akan menghasilkan hash berbeda.
Timestamp juga perlu diperiksa. Tolak request yang terlalu lama—umumnya lebih dari lima menit sesuai rekomendasi Slack—untuk mengurangi risiko replay. Pemeriksaan ini mengharuskan jam server tersinkronisasi, misalnya melalui NTP.
- Baca raw body tanpa mengubah isinya.
- Pastikan timestamp tersedia dan masih berada dalam jendela penerimaan.
- Hitung HMAC menggunakan signing secret.
- Bandingkan signature secara constant-time.
- Baru setelah itu parse JSON dan proses tipe event.
Signing secret membuktikan bahwa request berasal dari Slack, tetapi bukan pengganti otorisasi workspace dan channel. Setelah signature valid, aplikasi tetap harus menentukan instalasi, tenant, serta kebijakan akses yang sesuai.
URL verification dan event callback
Saat endpoint pertama kali dikonfigurasi, Slack dapat mengirim payload url_verification. Setelah signature diverifikasi, kembalikan nilai challenge sesuai kontrak Slack. Untuk payload event_callback, gunakan event_id sebagai identitas event dan periksa team_id, api_app_id, serta konteks otorisasinya sebelum membuat pekerjaan asinkron.
Abaikan event yang dibuat oleh bot sendiri atau subtype yang tidak didukung. Tanpa filter ini, bot dapat merespons pesannya sendiri dan membentuk loop.
ACK cepat dan antrean durable
Slack mengharapkan acknowledgement dalam hitungan detik. Target praktisnya adalah menyelesaikan validasi minimum, mencatat event secara durable, lalu segera mengembalikan HTTP 2xx. Model AI dipanggil oleh worker terpisah.
Jangan mengembalikan 2xx sebelum event benar-benar tersimpan. Jika server mati setelah ACK tetapi sebelum enqueue, Slack menganggap event sudah diterima dan pekerjaan dapat hilang. Gunakan transaksi database yang memasukkan receipt event sekaligus job atau outbox record.
OAuth Scope dengan Prinsip Least Privilege
OAuth scope harus berasal dari kebutuhan yang konkret, bukan dari keinginan untuk memberi bot akses seluas mungkin. Untuk bot yang hanya menjawab mention, titik awal yang umum adalah:
app_mentions:readuntuk menerima event mention yang relevan.chat:writeuntuk mengirim jawaban sebagai bot.- Scope riwayat channel hanya jika bot memang perlu membaca konteks tambahan.
Scope seperti channels:history, groups:history, im:history, atau mpim:history memperluas data yang dapat dibaca. Jangan meminta semuanya sebagai paket default. Hindari pula kemampuan mem-posting ke semua channel publik tanpa harus menjadi anggota jika alur produk tidak membutuhkannya.
Batasi channel dan thread pada lapisan aplikasi
OAuth scope menentukan batas maksimum dari Slack, tetapi aplikasi perlu menerapkan batas yang lebih sempit:
- Gunakan allowlist atau kebijakan channel per workspace.
- Secara default, tolak direct message, private channel, dan Slack Connect sampai use case serta kebijakan datanya disetujui.
- Ambil konteks hanya dari thread sumber, bukan seluruh riwayat channel.
- Balas pada
thread_tsyang sama. Untuk pesan induk, gunakan timestamp pesan sebagai awal thread. - Batasi jumlah, usia, dan ukuran pesan yang dimasukkan ke prompt.
- Jangan memindahkan isi thread ke channel lain atau workspace lain.
Isi pesan harus diperlakukan sebagai input tidak tepercaya. Instruksi di dalam thread tidak boleh otomatis mengubah system prompt, membuka secret, atau menjalankan tool. Otorisasi tool harus diperiksa oleh aplikasi, bukan diserahkan kepada keputusan model.
Isolasi workspace dan instalasi
Setiap instalasi Slack merupakan tenant keamanan. Simpan token berdasarkan identitas instalasi, misalnya kombinasi enterprise dan workspace yang sesuai dengan model distribusi aplikasi. Jangan memilih token hanya berdasarkan email pengguna, nama workspace, atau channel ID yang diterima dari client.
Lapisan penyimpanan sebaiknya menerapkan hal berikut:
- Enkripsi OAuth token saat disimpan dan batasi layanan yang dapat mendekripsinya.
- Sertakan
installation_idatau tenant key pada query data, job queue, cache, dan audit record. - Validasi bahwa channel dan user memang berada dalam konteks instalasi yang dipilih.
- Hapus atau nonaktifkan kredensial ketika aplikasi dicabut.
- Anggap Slack Connect sebagai batas kepercayaan tambahan karena peserta channel dapat berasal dari organisasi lain.
Pada OAuth redirect, validasi parameter state untuk mencegah request forgery. Setelah instalasi selesai, simpan scope aktual yang diberikan dan tolak fitur yang membutuhkan scope di luar izin tersebut.
Alur Request Bot AI di Slack
Arsitektur yang memisahkan penerimaan event dari pemrosesan AI membuat retry lebih aman dan waktu ACK lebih terprediksi.
- Slack mengirim request Events API ke endpoint publik.
- Gateway membaca raw body, memeriksa timestamp, dan memverifikasi signature.
- Handler memvalidasi tipe payload, instalasi, event, channel, dan identitas bot.
- Transaksi database memasukkan receipt unik berdasarkan aplikasi dan
event_id, lalu membuat job durable. - Endpoint segera mengembalikan HTTP 2xx.
- Worker mengambil job, memuat konteks thread yang diizinkan, dan melakukan redaksi atau klasifikasi data.
- Worker memanggil model dengan timeout dan batas penggunaan yang jelas.
- Output melewati pemeriksaan kebijakan, pelabelan AI, dan human review bila berisiko.
- Publisher memperoleh hak tunggal untuk mengirim respons, lalu memanggil
chat.postMessage. - Status pengiriman serta metadata audit dicatat tanpa menyimpan konten sensitif secara default.
Deduplikasi dengan event_id
Slack dapat mengirim ulang event ketika ACK terlambat, koneksi terputus, atau endpoint mengembalikan kegagalan. Header retry berguna untuk observabilitas, tetapi bukan sumber kebenaran untuk deduplikasi. Request awal dan retry harus bertemu pada unique constraint yang sama.
Gunakan kunci seperti (api_app_id, event_id). Unique constraint database lebih aman daripada pola check then insert, karena dua instance handler dapat menerima event yang sama secara bersamaan.
function handleSlackEvent(request):
raw = request.rawBody
timestamp = request.header('X-Slack-Request-Timestamp')
signature = request.header('X-Slack-Signature')
if not validTimestamp(timestamp, maxAge = 5 minutes):
return http(401)
if not validSlackSignature(raw, timestamp, signature):
return http(401)
payload = parseJson(raw)
if payload.type == 'url_verification':
return http(200, payload.challenge)
if payload.type != 'event_callback':
return http(200)
installation = findInstallation(payload)
if installation is null or installation.disabled:
return http(200)
event = payload.event
if event.bot_id is not null or not policyAllows(installation, event):
recordMinimalRejection(payload.event_id, installation.id)
return http(200)
inserted = transaction:
receipt = insertEventReceiptIfAbsent(
appId = payload.api_app_id,
eventId = payload.event_id,
installationId = installation.id,
status = 'accepted'
)
if receipt.wasInserted:
insertDurableJob(receipt.id)
return receipt.wasInserted
return http(200)Duplicate event tetap mendapat respons 2xx, tetapi tidak membuat job baru. Simpan receipt lebih lama daripada jendela retry yang ingin dilindungi. Kebijakan retensi harus mempertimbangkan kebutuhan operasional tanpa mempertahankan payload mentah.
Retry worker dan pencegahan respons ganda
Idempotensi inbound belum otomatis membuat pengiriman outbound idempoten. Ada kondisi sulit: Slack berhasil menerima pesan, tetapi koneksi terputus sebelum worker menerima respons API. Database tidak dapat mengetahui dengan pasti apakah pesan sudah terkirim.
Gunakan response ledger dengan unique key, misalnya (event_receipt_id, response_kind), serta status pending, sending, sent, unknown, dan failed. Hanya satu worker yang boleh mengubah pending menjadi sending.
function processJob(job):
response = createResponseOnce(job.receiptId, kind = 'thread_reply')
if response.status == 'sent':
return
if response.status in ['sending', 'unknown']:
scheduleReconciliation(response.id)
return
context = loadAllowedThreadContext(job)
result = callModelWithTimeout(context)
checked = applyOutputPolicy(result)
message = addAiLabel(checked.text)
if checked.requiresHumanReview:
moveToReviewQueue(response.id, message)
return
if not compareAndSet(response.status, 'pending', 'sending'):
return
try:
sent = slackPostMessage(
channel = job.channelId,
threadTs = job.threadTs,
text = message
)
markSent(response.id, slackMessageTs = sent.ts)
catch DefiniteApiFailure as error:
markFailedOrRetry(response.id, error)
catch AmbiguousNetworkFailure as error:
markUnknown(response.id, error)
scheduleReconciliation(response.id)Pengiriman ke sistem eksternal tidak dapat dijadikan transaksi atomik dengan database lokal. Karena itu, klaim exactly once biasanya tidak realistis. Pada timeout ambigu, jangan langsung mengirim ulang. Rekonsiliasi menggunakan data yang diizinkan, atau eskalasikan untuk pemeriksaan. Untuk bot yang sensitif terhadap kepercayaan, satu jawaban yang tertunda sering lebih aman daripada dua jawaban berbeda.
Untuk kegagalan definitif seperti rate limit, hormati petunjuk retry dari Slack, tambahkan jitter, dan batasi jumlah percobaan. Jangan membiarkan retry worker berlangsung tanpa batas.
Audit Output Tanpa Menyimpan Data Sensitif
Label dan provenance
Setiap jawaban sebaiknya menyatakan bahwa output dibuat AI. Label dapat dibuat singkat dan konsisten, misalnya “Dihasilkan oleh AI; verifikasi sebelum digunakan untuk keputusan penting.” Jika jawaban menggunakan sumber internal, tampilkan referensi yang benar-benar dipakai dan dapat diakses pengguna. Jangan membuat sitasi berdasarkan tebakan model.
Untuk aksi berisiko—mengubah konfigurasi, mengirim pesan massal, membuat tiket keamanan, menyetujui transaksi, atau menghapus data—model hanya boleh menghasilkan usulan. Eksekusi membutuhkan validasi deterministik dan persetujuan manusia yang memiliki wewenang.
Data audit minimum
Audit log tidak harus menyimpan prompt dan jawaban penuh. Secara default, catat metadata berikut:
- ID event, instalasi, job, dan response ledger.
- Timestamp penerimaan, ACK, pemrosesan, review, dan pengiriman.
- Channel ID yang dipseudonimkan jika identitas asli tidak diperlukan.
- Jenis event, keputusan kebijakan, serta alasan penolakan terstruktur.
- Identitas konfigurasi model atau prompt template, bukan secret atau isi prompt.
- Jumlah input/output atau kelas ukuran bila tersedia dari penyedia.
- Hash output untuk korelasi, bukan sebagai pengganti enkripsi.
- Status pengiriman, kode kesalahan yang disanitasi, dan jumlah retry.
- Identitas reviewer serta keputusan review untuk aksi berisiko.
Hash tidak menyembunyikan data dengan ruang kemungkinan kecil, sehingga jangan menganggap hashing selalu anonim. Hindari memasukkan teks pesan, nama, email, token, header otorisasi, atau respons model mentah ke log aplikasi dan platform observability.
Jika konten perlu disimpan untuk investigasi kualitas, gunakan jalur terpisah: persetujuan yang jelas, redaksi, enkripsi, akses berbasis peran, retention period pendek, dan audit akses. Sampling untuk evaluasi juga harus tunduk pada kebijakan data yang sama.
Kill switch dan fallback model
Kill switch perlu dapat menghentikan pemrosesan tanpa deployment baru. Terapkan setidaknya pada tingkat global, workspace, dan fitur berisiko. Worker harus memeriksanya sebelum memanggil model dan sekali lagi sebelum mengirim atau menjalankan aksi.
Ketika model gagal, fallback yang aman bukan selalu model kedua. Urutan yang lebih dapat diprediksi adalah:
- Batalkan cepat saat timeout atau circuit breaker terbuka.
- Jangan menjalankan aksi atau membuat jawaban faktual dari data parsial.
- Kirim pesan deterministik yang menjelaskan bahwa permintaan belum dapat diproses.
- Sediakan jalur retry manual atau eskalasi ke manusia.
- Gunakan model cadangan hanya jika kebijakan data, kemampuan, dan format output-nya telah diuji setara.
Contoh fallback yang aman adalah: “Bot AI belum dapat memproses permintaan ini. Tidak ada tindakan yang dijalankan. Silakan coba kembali atau hubungi pemilik layanan.” Pesan tersebut lebih baik daripada model yang mengarang hasil untuk menutupi kegagalan.
Matriks Failure Mode
| Failure mode | Gejala | Penanganan aman | Risiko jika diabaikan |
|---|---|---|---|
| Signature tidak valid | Request tidak cocok dengan HMAC | Tolak sebelum parse atau enqueue; catat metadata minimum | Event palsu dan penyalahgunaan bot |
| Timestamp terlalu lama | Request valid secara kriptografis tetapi kedaluwarsa | Tolak sebagai kemungkinan replay; periksa sinkronisasi jam | Event lama diproses kembali |
| ACK terlambat | Slack melakukan retry | Enqueue durable lalu segera 2xx; pindahkan AI ke worker | Duplikasi dan antrean retry |
| Event dikirim ulang | event_id yang sama muncul lagi | Unique constraint dan ACK 2xx tanpa job baru | Respons ganda |
| Dua worker memproses job | Status berubah bersamaan | Lease atau compare-and-set pada response ledger | Dua panggilan model dan dua pesan |
| Timeout setelah post | Status pengiriman tidak diketahui | Tandai unknown; rekonsiliasi, jangan retry buta | Pesan duplikat |
| Slack API rate limit | Respons pembatasan permintaan | Hormati waktu retry, tambahkan jitter, batasi percobaan | Thundering herd dan pemblokiran lebih lama |
| OAuth token dicabut | Panggilan API tidak lagi terotorisasi | Nonaktifkan instalasi, hentikan retry, minta instalasi ulang | Retry tanpa akhir dan alarm palsu |
| Model timeout atau unavailable | Tidak ada output yang dapat dipercaya | Circuit breaker dan fallback deterministik | Antrean menumpuk atau jawaban spekulatif |
| Output melanggar kebijakan | Data sensitif atau instruksi berisiko terdeteksi | Blokir, redaksi, atau kirim ke human review | Kebocoran data dan tindakan tidak sah |
| Tenant salah dipilih | Token atau konteks workspace tidak cocok | Fail closed dan validasi installation key pada setiap query | Kebocoran lintas-workspace |
| Kill switch aktif | Fitur dinonaktifkan oleh operator | Hentikan model dan aksi; gunakan pesan status terkontrol | Insiden terus berlangsung |
Pengujian dan Kontrak Operasional
Pengujian bot AI perlu mencakup kegagalan transport, konkurensi, dan kebijakan, bukan hanya kualitas jawaban model.
Contract test untuk endpoint
- Request dengan signature valid dan raw body yang tidak berubah.
- Signature salah, timestamp hilang, dan timestamp kedaluwarsa.
- Payload
url_verificationsertaevent_callback. - Event dari bot sendiri dan subtype yang tidak didukung.
- Instalasi tidak dikenal, dicabut, atau dinonaktifkan.
- Payload valid tetapi channel tidak berada dalam allowlist.
Pengujian idempotensi dan konkurensi
- Kirim
event_idyang sama berkali-kali secara paralel dan pastikan hanya satu job dibuat. - Jalankan dua worker pada job yang sama dan pastikan hanya satu memperoleh hak kirim.
- Matikan worker sesudah Slack menerima pesan tetapi sebelum status
senttersimpan. - Simulasikan database gagal sebelum dan sesudah transaksi enqueue.
- Pastikan retry tidak menembus batas workspace, channel, atau thread.
Pengujian keamanan dan output
- Masukkan prompt injection yang meminta token, system prompt, atau data channel lain.
- Pastikan bot tidak menjalankan tool hanya karena diperintah oleh teks Slack.
- Uji redaksi data pribadi serta secret pada log, trace, dan error report.
- Pastikan label AI tetap ada pada jawaban normal maupun fallback.
- Uji bahwa human review wajib untuk setiap aksi berisiko.
- Aktifkan kill switch saat job sedang berjalan dan pastikan pemeriksaan sebelum pengiriman mencegah aksi.
Monitor metrik seperti latensi ACK, jumlah duplicate event, usia antrean, status unknown, rate limit, kegagalan otorisasi, hasil review, dan aktivasi fallback. Gunakan ID korelasi, bukan payload mentah, agar investigasi tetap mungkin tanpa memperluas penyimpanan data sensitif.
Checklist Implementasi
- Verifikasi signature menggunakan raw body dan periksa usia timestamp.
- Simpan event serta job secara atomik sebelum memberikan ACK.
- Terapkan unique constraint pada identitas event.
- Pisahkan receipt inbound dari response ledger outbound.
- Jangan retry otomatis saat hasil pengiriman bersifat ambigu.
- Minta OAuth scope minimum dan terapkan pembatasan tambahan di aplikasi.
- Isolasi token, cache, job, audit, dan data berdasarkan instalasi.
- Ambil konteks hanya dari channel dan thread yang diizinkan.
- Labeli output sebagai buatan AI dan perlakukan output sebagai data tidak tepercaya.
- Catat metadata audit minimum tanpa prompt atau jawaban sensitif secara default.
- Wajibkan human review untuk aksi berisiko.
- Sediakan kill switch, circuit breaker, fallback deterministik, dan prosedur rekonsiliasi.
Kepercayaan terhadap bot AI di Slack terutama dibangun melalui perilaku yang dapat diprediksi saat sistem gagal. ACK cepat, idempotensi, least privilege, isolasi tenant, serta audit yang menjaga privasi membuat kegagalan dapat dibatasi dan dijelaskan—bukan berubah menjadi pesan ganda, kebocoran konteks, atau tindakan yang tidak dapat dipertanggungjawabkan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!