Tiga pola tersebut adalah integrasi API langsung, integrasi event-driven, dan pemrosesan async berbasis antrian. Integrasi API langsung cocok untuk kueri real-time dan tindakan yang membutuhkan respons segera, seperti pengecekan status pembayaran. Integrasi event-driven paling tepat untuk notifikasi, logging, dan analitik di mana sistem hilir tidak perlu merespons ke ERP. Pemrosesan berbasis antrian adalah pilihan yang tepat untuk operasi seperti pengiriman email, pembuatan PDF, dan panggilan API eksternal yang mungkin gagal dan perlu percobaan ulang.
Dead Letter Queue menampung pekerjaan yang telah menghabiskan semua percobaan ulang, menyimpan konteks lengkap — payload pekerjaan, pesan kesalahan dari setiap percobaan, dan timestamp. Meninjau DLQ setiap hari memungkinkan tim mendeteksi pola yang mengindikasikan perubahan kontrak API, kedaluwarsa autentikasi, atau masalah kualitas data sebelum masalah tersebut muncul di log aplikasi. Tanpa DLQ, pekerjaan integrasi yang gagal hilang begitu saja dan penyebab akar masalah menjadi sangat sulit didiagnosis.
Kunci idempotency adalah ID unik yang disertakan dalam setiap permintaan API sehingga jika timeout memaksa percobaan ulang, sistem penerima mengenali duplikat dan tidak membuat transaksi kedua. Circuit breaker menghentikan percobaan ulang ke API eksternal yang gagal setelah sejumlah kegagalan berturut-turut yang dikonfigurasi, membuat sistem gagal cepat dan memicu peringatan daripada terus menghantam layanan yang tidak tersedia. Keduanya bersama mencegah transaksi duplikat dan mengurangi kegagalan beruntun akibat API eksternal yang lambat atau mati.
Artikel ini menekankan pentingnya selalu memvalidasi tanda tangan webhook. Payment gateway seperti Midtrans dan Xendit menyertakan tanda tangan di header webhook yang dihitung dari payload dan kunci rahasia bersama; ERP harus memverifikasi tanda tangan ini sebelum memproses permintaan. Langkah ini sering dilewati karena URL webhook tampak sudah cukup sebagai rahasia, padahal sistem mana pun yang mengetahui URL tersebut dapat mengirimkan payload sembarang — sehingga validasi tanda tangan menjadi satu-satunya pertahanan yang andal.
Tiga integrasi disorot sebagai wajib atau hampir wajib untuk bisnis Indonesia. E-Faktur (DJP) mengharuskan pengiriman data faktur dalam format CSV tertentu dan menerima NSFP sebagai balasannya, dan wajib bagi bisnis berstatus PKP. BPJS Kesehatan dan BPJS Ketenagakerjaan memerlukan pengiriman file kontribusi asuransi karyawan bulanan ke portal pemerintah. Integrasi payment gateway Midtrans atau Xendit menangani penagihan AR melalui pembuatan virtual account, webhook pembayaran real-time, dan rekonsiliasi dengan faktur AR.
Tidak ada ERP yang berdiri sendiri. Setiap bisnis memiliki ekosistem sistem yang harus bertukar data dengan ERP: payment gateway untuk penagihan AR, penyedia email untuk notifikasi otomatis, portal pajak pemerintah untuk pengiriman e-faktur, API perbankan untuk pemrosesan pembayaran, API logistik untuk pelacakan pengiriman. Mendapatkan integrasi yang salah menyebabkan korupsi data, transaksi duplikat, dan acara yang terlewat — masalah yang mahal untuk didiagnosis dan bahkan lebih mahal untuk diperbaiki dalam sistem produksi.
Untuk ERP kustom yang melayani UKM Indonesia, tiga pola mencakup sebagian besar kebutuhan integrasi. Integrasi API langsung: ERP memanggil API eksternal secara sinkron dan menunggu respons. Cocok untuk: kueri real-time, tindakan di mana pengguna mengharapkan respons segera. Integrasi event-driven: ERP menerbitkan acara dan satu atau lebih sistem hilir mengonsumsinya. Integrasi berbasis antrian: ERP menempatkan pekerjaan dalam antrian, pekerja memprosesnya dan menangani percobaan ulang pada kegagalan.
Integrasi API langsung adalah pola yang paling umum dan paling rapuh. Jika API eksternal mati, permintaan ERP Anda gagal. Desain integrasi yang andal memecahkan masalah ini dengan tiga teknik: kunci idempotency (setiap permintaan API menyertakan ID unik yang mencegah pemrosesan duplikat), circuit breakers (hentikan percobaan ulang API yang gagal setelah N kegagalan berturut-turut), dan async fallback (konversi panggilan sinkron ke pekerjaan antrian async).
Untuk ERP yang dibangun di NestJS, BullMQ (antrian pekerjaan yang didukung Redis) adalah pilihan standar untuk pemrosesan integrasi async. Polanya: ketika ERP perlu memanggil API eksternal (kirim email, hasilkan PDF, kirimkan e-faktur), alih-alih memanggil API secara langsung, ERP membuat pekerjaan dalam antrian BullMQ dan segera kembali ke pengguna. Proses pekerja mengambil pekerjaan, mengeksekusi panggilan API, menangani kegagalan dengan logika percobaan ulang yang dapat dikonfigurasi.
ERP Integration Architecture — Queue-Based Async
┌──────────────────────────────────────────────────────────────┐
│ ERP Application (NestJS) │
│ │
│ User Action → Service → BullMQ Queue → Response to User │
│ ↓ │
│ ┌──────────┐ │
│ │ Redis │ (job storage) │
│ └──────┬───┘ │
└────────────────────────────────┼─────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ Worker Processes │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐│
│ │ Email Worker │ │ PDF Worker │ │ e-Faktur Worker ││
│ │ (SendGrid) │ │ (Puppeteer) │ │ (DJP API) ││
│ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘│
│ │ │ │ │
│ ┌──────▼──────────────────▼─────────────────────▼─────────┐│
│ │ Circuit Breaker + Retry Logic ││
│ │ Attempt 1 → wait 1s → Attempt 2 → wait 4s → Attempt 3 ││
│ │ On 3 failures: route to Dead Letter Queue (DLQ) ││
│ └──────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────┘
EXTERNAL SYSTEMS:
┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌────────┐
│ Midtrans │ │ SendGrid │ │ DJP e-Faktur│ │ BPJS │
│ (payments) │ │ (email) │ │ (tax portal)│ │(social)│
└─────────────┘ └─────────────┘ └──────────────┘ └────────┘
Dead Letter Queue review: check daily.
DLQ items = integration broken or data quality issue.Dari pengalaman saya sebagai konsultan ERP lepas: saya selalu mengimplementasikan dead letter queue (DLQ) untuk pekerjaan integrasi yang gagal. Pekerjaan yang menghabiskan percobaan ulang mereka harus masuk ke DLQ dengan konteks penuh — payload pekerjaan, pesan kesalahan dari setiap percobaan, dan timestamp. Saya meninjau DLQ setiap hari. Pola kegagalan dalam DLQ sering mengungkapkan perubahan kontrak API, kedaluwarsa autentikasi, atau masalah kualitas data.
API integrasi ERP Anda mendefinisikan cara sistem eksternal dapat membaca dan menulis data ke ERP. Rancang dengan prinsip-prinsip ini: versioning (awali semua endpoint API dengan /api/v1/), idempotency (endpoint POST menerima kunci idempotency yang disediakan klien), pagination (tidak pernah mengembalikan daftar tak terbatas), dan autentikasi (integrasi mesin-ke-mesin menggunakan kunci API atau JWT akun layanan).
// NestJS: BullMQ Integration Service with DLQ
import { InjectQueue } from '@nestjs/bullmq';
import { Queue, Worker, QueueEvents } from 'bullmq';
// Integration queue definitions
const QUEUES = {
EMAIL: 'email-queue',
PDF: 'pdf-queue',
EFAKTUR: 'efaktur-queue',
WEBHOOK: 'webhook-queue',
} as const;
// Service: Add jobs to queue (async — returns immediately to user)
@Injectable()
export class IntegrationService {
constructor(
@InjectQueue(QUEUES.EMAIL) private emailQueue: Queue,
@InjectQueue(QUEUES.EFAKTUR) private efakturQueue: Queue,
) {}
async sendInvoiceEmail(invoiceId: string, recipientEmail: string) {
await this.emailQueue.add(
'send-invoice-email',
{ invoiceId, recipientEmail },
{
attempts: 3,
backoff: { type: 'exponential', delay: 1000 },
removeOnComplete: { age: 86400 }, // keep 24hrs for debugging
removeOnFail: false, // DLQ — keep failed jobs forever
}
);
// Returns immediately. User gets "Email queued" response.
}
async submitEFaktur(invoiceId: string) {
await this.efakturQueue.add(
'submit-efaktur',
{ invoiceId },
{
attempts: 5,
backoff: { type: 'exponential', delay: 2000 },
// e-Faktur DJP API is slow — allow more retries
timeout: 30_000,
}
);
}
}
// Worker: Processes jobs (handles retries, logs failures)
@Processor(QUEUES.EMAIL)
export class EmailWorker extends WorkerHost {
async process(job: Job<{ invoiceId: string; recipientEmail: string }>) {
const { invoiceId, recipientEmail } = job.data;
const invoice = await this.invoiceService.findOne(invoiceId);
const pdfUrl = await this.pdfService.generateInvoicePdf(invoiceId);
await this.emailProvider.send({
to: recipientEmail,
subject: `Invoice ${invoice.number} from Commsult Indonesia`,
html: this.templates.invoiceEmail(invoice),
attachments: [{ url: pdfUrl, filename: `invoice-${invoice.number}.pdf` }],
});
// Log to audit trail on success
await this.auditService.log({
action: 'EMAIL_SENT',
resource: 'invoice',
resourceId: invoiceId,
metadata: { recipient: recipientEmail, jobId: job.id },
});
}
}
// Webhook validation — ALWAYS verify signatures
@Post('/webhooks/midtrans')
async handleMidtransWebhook(
@Headers('x-callback-token') token: string,
@Body() payload: MidtransWebhookPayload,
) {
// Verify signature before processing
const expectedToken = createHash('sha512')
.update(`${payload.order_id}${payload.status_code}${payload.gross_amount}${MIDTRANS_SERVER_KEY}`)
.digest('hex');
if (token !== expectedToken) {
this.logger.warn(`Invalid Midtrans webhook signature: ${payload.order_id}`);
throw new UnauthorizedException('Invalid webhook signature');
}
// Safe to process
await this.paymentService.processCallback(payload);
}Implementasi ERP Indonesia biasanya memerlukan integrasi yang bukan bagian dari perpustakaan konektor bawaan platform standar mana pun. E-Faktur (DJP): sistem faktur elektronik otoritas pajak memerlukan pengiriman data faktur dalam format CSV tertentu. BPJS Kesehatan dan BPJS Ketenagakerjaan: pelaporan bulanan kontribusi asuransi kesehatan dan ketenagakerjaan karyawan memerlukan pengiriman file dalam format tertentu ke portal pemerintah. Midtrans atau Xendit: integrasi payment gateway untuk penagihan AR.
Webhook — callback HTTP dari sistem eksternal ke ERP Anda — adalah pola integrasi umum untuk konfirmasi pembayaran, pembaruan status pengiriman, dan hasil persetujuan. Tetapi webhook juga merupakan risiko keamanan: sistem mana pun yang mengetahui URL webhook Anda dapat mengirimkan payload arbitrer kepada Anda. Selalu validasi tanda tangan webhook. Payment gateway (Midtrans, Xendit) menyertakan tanda tangan di header webhook yang dihitung dari payload dan kunci rahasia Anda. Verifikasi tanda tangan ini sebelum memproses webhook.
Kegagalan integrasi tidak terlihat kecuali Anda secara aktif memantaunya. Bangun dashboard kesehatan integrasi yang menampilkan: kedalaman antrian pekerjaan, tingkat kegagalan pekerjaan berdasarkan jenis integrasi, jumlah item dead letter queue, tingkat kesalahan dan latensi API eksternal, dan tingkat keberhasilan pengiriman webhook.
Setiap integrasi harus didokumentasikan dalam kontrak integrasi: nama dan versi sistem eksternal, pola integrasi, data yang dipertukarkan, metode autentikasi, perilaku penanganan kesalahan, throughput dan latensi yang diharapkan, dan pemilik yang bertanggung jawab memelihara integrasi. Ketika penyedia API eksternal mengubah API mereka, kontrak integrasi memberi tahu Anda persis kode mana yang perlu diperbarui.