# Integrating Third-Party APIs with a Custom ERP: Patterns, Webhooks, and Error Handling

> A practical guide to connecting your custom ERP with external systems — payment gateways, logistics APIs, e-invoicing platforms, and government APIs — using NestJS middleware patterns.

URL: https://www.matthewswong.com/en/blog/erp-third-party-api-integration/

- Author: Matthews Wong (https://www.matthewswong.com/en/about/)
- Published: 2025-06-10
- Updated: 2025-06-10
- Category: ERP

Modern ERP systems don't operate in isolation. They need to talk to payment gateways, logistics platforms, e-invoicing systems, government portals, and marketplace APIs. Event-driven architecture with webhooks and message queues can reduce integration latency from minutes to seconds, according to API integration research. In Indonesia, this is particularly relevant: ERP systems need to connect to DJP's e-Faktur for VAT invoices, BPJS for social security reporting, and local payment gateways like Midtrans or Xendit. In my freelance ERP consulting work, I've integrated over a dozen external APIs into the custom ERP systems I build for clients. This post covers the patterns and pitfalls.

## The Three Integration Patterns

Every ERP-to-external integration falls into one of three patterns: synchronous REST API calls (ERP calls external API and waits for response), asynchronous webhooks (external system notifies ERP when something happens), and file-based batch exchange (daily file drops, CSV imports, SFTP). Each pattern has appropriate use cases. Synchronous is right for real-time queries (check a payment status). Webhooks are right for event notifications (payment confirmed, delivery status changed). Batch is right for high-volume data that doesn't require real-time updates (daily transaction reconciliation).

### Outbound Integration: Calling External APIs

For outbound API calls from NestJS, I use a dedicated IntegrationService per external system. Each service handles: authentication (API keys stored in environment variables, OAuth token refresh if needed), request building, response parsing, error classification (retry-able vs. permanent errors), and logging. I wrap all external calls in a Circuit Breaker pattern (using the opossum library) — if the external API fails repeatedly, the circuit opens and returns a cached or default response instead of hammering a failing service.

> From my experience as a freelance ERP consultant: never call an external API synchronously from a user request if the response is non-critical or can be retried. For example, when posting a payment, don't make the user wait for the payment gateway response — submit the payment to a Bull job queue, return an optimistic 'payment submitted' response to the user, and process the gateway call asynchronously. This keeps the ERP UI responsive even when external services are slow.

## Inbound Webhooks: Receiving External Events

Webhooks from external systems arrive at a NestJS endpoint as HTTP POST requests. The critical implementation requirements: validate the webhook signature (each provider has a different signing mechanism — Midtrans uses an MD5 hash, Xendit uses x-callback-token header, DHL uses HMAC-SHA256), make the handler idempotent (duplicate webhooks must not create duplicate records), and return 200 quickly (never do heavy processing synchronously in a webhook handler — push to a queue and return 200 within 2 seconds or the sender will retry).

### Message Queue Architecture for Reliable Integration

I use Bull (Redis-backed job queue) for all asynchronous integration processing. When a webhook arrives, the handler validates the signature, creates a job in Bull, and returns 200. The Bull worker processes the job: parses the event, updates ERP state, triggers downstream actions (e.g., mark invoice paid, send notification). If the processing fails, Bull retries with exponential backoff. After a configurable number of retries, jobs go to a dead-letter queue for manual review. This architecture means no webhook event is ever lost, even if the ERP is temporarily down when it arrives.

> **Warning: API Rate Limits and Credential Rotation**

> External APIs have rate limits. DJP's e-Faktur API has a per-hour limit that can easily be exceeded during end-of-month VAT invoice generation when many invoices are submitted at once. I batch e-Faktur submissions with a rate limiter (using bottleneck library in Node.js) to stay within limits. Separately: API credentials expire or get rotated. If you store API keys only in environment variables, a key rotation requires a server restart. Use a secrets management service (AWS Secrets Manager, HashiCorp Vault) or at minimum a database table for credentials with a reload mechanism.

## How I Approached This

My integration layer is a dedicated NestJS module with adapters per external system. The adapter interface is: connect(), healthCheck(), send(payload), and getStatus(referenceId). This uniform interface makes it easy to swap providers — I switched from one SMS gateway to another without changing any business logic, only the adapter implementation. The adapters are registered as NestJS providers with a symbol key, and the business services inject them by interface rather than concrete class.

## Indonesian-Specific Integrations

The integrations specific to Indonesian business context: DJP e-Faktur (PPN invoice submission via SOAP API — yes, it's SOAP, not REST), BPJS API (monthly contribution reporting), Bank Indonesia payment gateway APIs (SNAP — Standard National Open API for banking interoperability), and LKPP (government procurement portal) for companies that sell to government entities. Each of these has quirks: e-Faktur uses a certificate-based authentication and a SOAP envelope format; SNAP requires an HMAC-SHA512 signature with a specific timestamp format. Document these integration details thoroughly — they're poorly documented by the Indonesian government.

## Monitoring and Alerting for Integrations

Integration health monitoring is critical. I expose a /health/integrations endpoint that pings each external service and returns green/amber/red status. This is polled by my monitoring stack every 5 minutes. Failed integrations trigger an immediate Slack alert so I can respond right away. I also maintain an integration_events table: every outbound call and inbound webhook is logged with the external reference ID, payload hash, response status, and processing time. This log is indispensable when debugging integration discrepancies — 'did I actually send that e-Faktur?' is answered by querying the table.

## Sources & Further Reading

- Ecosire — API Integration Patterns: Enterprise Architecture Best Practices — https://ecosire.com/blog/api-integration-patterns-enterprise-guide
- Apideck — ERP API Integration: Understanding Business Cases and Technical Challenges — https://www.apideck.com/blog/erp-api-integration
- CoderRapper — ERP Integration Tools in 2025: iPaaS, APIs & Real Use Cases Explained — https://coderapper.com/article/erp%E2%80%91supply%E2%80%91chain/erp-integration-tools-for-ecommerce/

> **Key takeaway:** A custom ERP has to talk to payment gateways, logistics platforms, e-invoicing systems, government portals and marketplace APIs. Handling those through webhooks and a message queue rather than synchronous calls cuts integration latency from minutes to seconds, and keeps one slow third party from blocking a business transaction.

## Source links

- https://ecosire.com/blog/api-integration-patterns-enterprise-guide
- https://www.apideck.com/blog/erp-api-integration
- https://coderapper.com/article/erp%E2%80%91supply%E2%80%91chain/erp-integration-tools-for-ecommerce/

## FAQ

### What are the three main integration patterns when connecting a custom ERP to external systems?

The three patterns are synchronous REST API calls (the ERP calls the external API and waits for the response), asynchronous webhooks (the external system notifies the ERP when an event occurs), and file-based batch exchange (daily file drops, CSV imports, or SFTP). Synchronous suits real-time queries like checking a payment status, webhooks suit event notifications like a confirmed payment, and batch suits high-volume data that does not require real-time updates such as daily reconciliation.

### Why should payment gateway calls be handled asynchronously rather than inline with the user request?

Calling a payment gateway synchronously forces the user to wait for an external service that may be slow or temporarily unavailable, making the ERP UI feel unresponsive. The recommended approach is to submit the payment to a Bull job queue and return an optimistic 'payment submitted' response immediately, then process the gateway call in the background. This keeps the user experience fast even when the external service is sluggish.

### How does the architecture prevent webhook events from being lost or processed twice?

When a webhook arrives, the NestJS handler validates the signature, immediately creates a Bull (Redis-backed) job, and returns HTTP 200 — keeping processing time under 2 seconds so the sender does not retry. The Bull worker then processes the job with exponential backoff retries on failure, and unresolvable jobs land in a dead-letter queue for manual review. Making handlers idempotent ensures that duplicate webhooks cannot create duplicate records.

### What is the Circuit Breaker pattern and why is it used for external API calls in the ERP?

The Circuit Breaker pattern (implemented with the opossum library) monitors outbound calls to an external API and, if that API fails repeatedly, opens the circuit and returns a cached or default response instead of continuing to call the failing service. This prevents a degraded external dependency from cascading into ERP-wide slowdowns. It also reduces unnecessary load on an already struggling third-party service.

### What Indonesian-specific API integrations does the post cover, and what are their main technical quirks?

The post covers DJP e-Faktur for VAT invoice submission (which uses a certificate-based SOAP API, not REST), BPJS for monthly social-security contribution reporting, Bank Indonesia's SNAP payment gateway (requiring HMAC-SHA512 signatures with a specific timestamp format), and LKPP for government procurement. Because official documentation for these integrations is sparse, the post recommends documenting integration details thoroughly internally.

---

- HTML version: https://www.matthewswong.com/en/blog/erp-third-party-api-integration/
- Bahasa Indonesia: https://www.matthewswong.com/id/blog/erp-third-party-api-integration/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
