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.

  1. Baca raw body tanpa mengubah isinya.
  2. Pastikan timestamp tersedia dan masih berada dalam jendela penerimaan.
  3. Hitung HMAC menggunakan signing secret.
  4. Bandingkan signature secara constant-time.
  5. 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:read untuk menerima event mention yang relevan.
  • chat:write untuk 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_ts yang 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_id atau 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.

  1. Slack mengirim request Events API ke endpoint publik.
  2. Gateway membaca raw body, memeriksa timestamp, dan memverifikasi signature.
  3. Handler memvalidasi tipe payload, instalasi, event, channel, dan identitas bot.
  4. Transaksi database memasukkan receipt unik berdasarkan aplikasi dan event_id, lalu membuat job durable.
  5. Endpoint segera mengembalikan HTTP 2xx.
  6. Worker mengambil job, memuat konteks thread yang diizinkan, dan melakukan redaksi atau klasifikasi data.
  7. Worker memanggil model dengan timeout dan batas penggunaan yang jelas.
  8. Output melewati pemeriksaan kebijakan, pelabelan AI, dan human review bila berisiko.
  9. Publisher memperoleh hak tunggal untuk mengirim respons, lalu memanggil chat.postMessage.
  10. 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:

  1. Batalkan cepat saat timeout atau circuit breaker terbuka.
  2. Jangan menjalankan aksi atau membuat jawaban faktual dari data parsial.
  3. Kirim pesan deterministik yang menjelaskan bahwa permintaan belum dapat diproses.
  4. Sediakan jalur retry manual atau eskalasi ke manusia.
  5. 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 modeGejalaPenanganan amanRisiko jika diabaikan
Signature tidak validRequest tidak cocok dengan HMACTolak sebelum parse atau enqueue; catat metadata minimumEvent palsu dan penyalahgunaan bot
Timestamp terlalu lamaRequest valid secara kriptografis tetapi kedaluwarsaTolak sebagai kemungkinan replay; periksa sinkronisasi jamEvent lama diproses kembali
ACK terlambatSlack melakukan retryEnqueue durable lalu segera 2xx; pindahkan AI ke workerDuplikasi dan antrean retry
Event dikirim ulangevent_id yang sama muncul lagiUnique constraint dan ACK 2xx tanpa job baruRespons ganda
Dua worker memproses jobStatus berubah bersamaanLease atau compare-and-set pada response ledgerDua panggilan model dan dua pesan
Timeout setelah postStatus pengiriman tidak diketahuiTandai unknown; rekonsiliasi, jangan retry butaPesan duplikat
Slack API rate limitRespons pembatasan permintaanHormati waktu retry, tambahkan jitter, batasi percobaanThundering herd dan pemblokiran lebih lama
OAuth token dicabutPanggilan API tidak lagi terotorisasiNonaktifkan instalasi, hentikan retry, minta instalasi ulangRetry tanpa akhir dan alarm palsu
Model timeout atau unavailableTidak ada output yang dapat dipercayaCircuit breaker dan fallback deterministikAntrean menumpuk atau jawaban spekulatif
Output melanggar kebijakanData sensitif atau instruksi berisiko terdeteksiBlokir, redaksi, atau kirim ke human reviewKebocoran data dan tindakan tidak sah
Tenant salah dipilihToken atau konteks workspace tidak cocokFail closed dan validasi installation key pada setiap queryKebocoran lintas-workspace
Kill switch aktifFitur dinonaktifkan oleh operatorHentikan model dan aksi; gunakan pesan status terkontrolInsiden 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_verification serta event_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_id yang 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 sent tersimpan.
  • 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.