Membangun Approval Gate di n8n untuk Otomasi yang Aman dan Terukur
Otomasi yang langsung mengeksekusi aksi penting memang terlihat efisien. Masalahnya muncul ketika data sumber keliru, nilai transaksi melewati batas, atau kredensial yang dipakai workflow memiliki akses terlalu luas. Di titik itu, workflow membutuhkan satu mekanisme sederhana tetapi penting: approval gate.
Approval gate membuat workflow berhenti sebelum aksi berisiko dijalankan. Sistem mengirim ringkasan permintaan kepada approver, mencatat keputusan, lalu hanya meneruskan proses jika keputusan memenuhi aturan yang ditetapkan.
Tutorial ini memakai contoh pengajuan perubahan status invoice. Polanya dapat diterapkan untuk deployment, pembuatan user, pengiriman email massal, perubahan data pelanggan, atau proses bisnis lain yang memiliki dampak nyata.
Arsitektur workflow
Alur yang akan dibangun:
- Webhook menerima pengajuan.
- Set menormalisasi field penting.
- Code memvalidasi format dan menghitung tingkat risiko.
- IF menolak input yang tidak valid.
- Postgres menyimpan permintaan dengan status
pending. - Slack mengirim permintaan approval beserta tautan keputusan.
- Wait menghentikan eksekusi sampai keputusan diterima.
- Webhook approval menerima
approveataurejectdengan token sekali pakai. - IF memeriksa keputusan dan masa berlaku token.
- HTTP Request atau node bisnis menjalankan aksi hanya setelah disetujui.
- Postgres menyimpan hasil akhir dan timestamp.
Untuk deployment production, pisahkan endpoint pengajuan dan endpoint approval. Jangan menaruh token approval di URL yang mudah dibagikan ke pihak yang tidak berkepentingan.
Menyiapkan tabel audit
Gunakan tabel sederhana berikut di PostgreSQL:
create table automation_approvals (
id uuid primary key,
request_type varchar(80) not null,
requested_by varchar(160) not null,
payload jsonb not null,
risk_level varchar(20) not null,
status varchar(20) not null default 'pending',
approval_token_hash varchar(128) not null,
approved_by varchar(160),
decision_at timestamptz,
executed_at timestamptz,
result jsonb,
created_at timestamptz not null default now(),
expires_at timestamptz not null
);
Simpan hash token, bukan token mentah. Tambahkan index pada status, expires_at, dan approval_token_hash agar pemeriksaan cepat dan token yang sudah dipakai dapat diblokir.
Membuat input dan validasi
Buat node Webhook dengan metode POST. Contoh payload:
{
"request_type": "invoice_status_change",
"invoice_id": "INV-2026-0091",
"new_status": "paid",
"amount": 12500000,
"requested_by": "[email protected]",
"reason": "Pembayaran telah diverifikasi"
}
Pada node Code, lakukan validasi eksplisit. Jangan menganggap field ada hanya karena workflow berhasil menerima HTTP request.
const input = $json;
const errors = [];
if (!/^INV-[0-9]{4}-[0-9]{4}$/.test(input.invoice_id ?? '')) {
errors.push('invoice_id tidak valid');
}
if (!['paid', 'cancelled'].includes(input.new_status)) {
errors.push('new_status tidak diizinkan');
}
const amount = Number(input.amount);
if (!Number.isFinite(amount) || amount <= 0) {
errors.push('amount harus berupa angka positif');
}
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(input.requested_by ?? '')) {
errors.push('requested_by tidak valid');
}
if (errors.length) {
return [{ json: { valid: false, errors } }];
}
const riskLevel = amount >= 10000000 ? 'high' : amount >= 2000000 ? 'medium' : 'low';
return [{
json: {
valid: true,
request_type: input.request_type,
invoice_id: input.invoice_id,
new_status: input.new_status,
amount,
requested_by: input.requested_by,
reason: String(input.reason ?? '').slice(0, 500),
risk_level: riskLevel
}
}];
Hubungkan ke node IF. Cabang valid = false harus mengembalikan HTTP 422 dan menulis log penolakan. Cabang valid melanjutkan ke penyimpanan audit.
Menentukan aturan approval
Tetapkan aturan yang dapat dibaca manusia dan mudah diuji:
| Risiko | Nilai | Approver |
|---|---|---|
| low | di bawah Rp2 juta | supervisor finance |
| medium | Rp2 juta sampai kurang dari Rp10 juta | manager finance |
| high | Rp10 juta atau lebih | manager finance dan kepala operasional |
Jangan mengandalkan data dari request untuk menentukan siapa approver. Mapping approver harus berada di konfigurasi server atau tabel yang dikendalikan administrator.
Simpan request dengan status pending dan expires_at misalnya 24 jam dari waktu pembuatan. Setelah insert berhasil, buat token acak dengan node Code, kirim hash token ke database, dan kirim tautan approval melalui Slack. Untuk workflow dengan risiko tinggi, buat dua approval terpisah dan eksekusi hanya jika keduanya sudah diterima.
Membuat pesan approval yang jelas
Pesan Slack sebaiknya memuat:
- jenis permintaan dan ID invoice
- nilai dan level risiko
- pemohon
- alasan
- batas waktu approval
- tombol atau tautan approve dan reject
- peringatan bahwa aksi akan mengubah data bisnis
Hindari memasukkan access token, data kartu, password, atau payload sensitif ke pesan. Tampilkan hanya informasi yang diperlukan approver untuk mengambil keputusan.
Menunggu dan memproses keputusan
Gunakan node Wait atau pola callback berbasis webhook. Endpoint keputusan harus memeriksa:
- request masih berstatus
pending - token belum kedaluwarsa
- token cocok dengan hash di database
- identitas approver memiliki hak untuk request tersebut
- keputusan hanya
approveataureject - request belum pernah dieksekusi
Gunakan operasi database atomik agar dua klik approve yang hampir bersamaan tidak memicu eksekusi ganda. Contoh konsep query:
update automation_approvals
set status = 'approved', approved_by = $1, decision_at = now()
where id = $2
and status = 'pending'
and expires_at > now()
returning id;
Jika query tidak mengembalikan baris, perlakukan sebagai keputusan yang terlambat atau sudah diproses. Jangan lanjutkan ke node bisnis.
Eksekusi dengan idempotensi
Sebelum node yang mengubah invoice berjalan, buat kunci idempotensi seperti invoice_status_change:INV-2026-0091:paid. Simpan kunci tersebut di database atau Redis dengan TTL yang sesuai. Jika kunci sudah ada, workflow harus berhenti dan mencatat bahwa request merupakan duplikasi.
Node aksi juga harus memakai credential dengan izin minimum. Untuk perubahan status invoice, credential tidak seharusnya dapat menghapus invoice atau membaca data di luar kebutuhan workflow.
Setelah aksi selesai, simpan response yang sudah disanitasi ke kolom result, ubah status menjadi executed, dan isi executed_at. Jika aksi gagal, ubah status menjadi execution_failed, kirim alert ke kanal operasional, lalu gunakan retry terbatas dengan backoff. Jangan melakukan retry tanpa batas pada aksi yang tidak idempotent.
Validasi sebelum production
Uji workflow dengan daftar kasus berikut:
- payload tanpa
invoice_id - format invoice salah
- amount berupa string nonangka
- amount nol atau negatif
- status di luar daftar yang diizinkan
- token salah
- token kedaluwarsa
- approver tidak berwenang
- dua approval dikirim bersamaan
- request yang sama dikirim dua kali
- node aksi mengalami timeout
- database audit tidak tersedia
Workflow hanya boleh dinyatakan siap jika setiap kasus menghasilkan status yang dapat diprediksi, tidak ada aksi bisnis untuk input invalid, dan setiap keputusan meninggalkan jejak audit.
Tambahkan execution retention yang sesuai kebijakan, alert untuk workflow gagal, serta dashboard sederhana yang memantau jumlah pending, expired, execution_failed, dan waktu approval rata-rata.
Guardrail yang jangan dilewatkan
Approval gate bukan pengganti kontrol akses. Terapkan defense in depth:
- validasi schema di boundary
- allowlist untuk operasi yang boleh dijalankan
- credential least privilege
- token sekali pakai dengan masa berlaku
- audit log yang tidak mudah diubah
- idempotensi untuk aksi bisnis
- timeout dan retry terbatas
- notifikasi kegagalan ke pemilik layanan
- pemisahan environment development dan production
- review berkala terhadap daftar approver
Dengan pola ini, n8n tetap bisa menghemat pekerjaan manual tanpa mengubah workflow menjadi jalur eksekusi yang tidak diawasi. Otomasi yang baik bukan sekadar berjalan cepat, tetapi juga dapat dihentikan, dijelaskan, dan dipulihkan ketika kondisi tidak sesuai harapan.