ERP Integration Architecture

Design reliable integrations between ERP systems and the rest of your technology landscape. Covers middleware patterns, API vs file-based integration, idempotency, error handling, master data synchronization, and the patterns that keep ERP integrations stable.

ERP integration architecture defines the structure, protocols, and operational patterns for connecting an enterprise resource planning (ERP) system with other business systems—such as CRM, HRIS, e-commerce, supply chain, and analytics platforms—through consistent, reliable, and scalable data exchange. This architecture matters most during system migrations, when new business capabilities are introduced, or when an organization scales beyond a single ERP instance. It is the backbone of real-time, bidirectional, and event-driven data flows across enterprise boundaries.

Core Integration Patterns

Synchronous Request-Response with HTTP APIs

When immediate feedback is critical—such as confirming a sales order in ERP before completing a checkout—synchronous HTTP APIs are the foundation. The ERP exposes endpoints using standard REST principles: JSON payloads, HTTP status codes, and request/response headers.

A typical integration request uses POST /api/salesorders with a JSON body:

{
  "customerNumber": "CUST-10042",
  "orderDate": "2023-10-15",
  "items": [
    {
      "productId": "PROD-001",
      "quantity": 5,
      "unitPrice": 29.99
    }
  ]
}

The ERP responds with a 201 Created status and a Location header pointing to the new order:

HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/salesorders/12345

{
  "orderId": "12345",
  "status": "Confirmed",
  "totalAmount": 149.95,
  "createdAt": "2023-10-15T10:30:00Z"
}

Common failures include:

  {
    "validationErrors": [
      {
        "field": "customerNumber",
        "message": "Customer number must be at least 6 characters long"
      },
      {
        "field": "items[0].quantity",
        "message": "Quantity must be a positive integer"
      }
    ]
  }

The integration must handle partial success: when one item fails but the order is created, the ERP returns 207 Multi-Status, with a body structured as:

{
  "statusCode": 207,
  "multiStatus": [
    {
      "statusCode": 201,
      "resource": "/api/salesorders/12345",
      "headers": { "ETag": "W/\"abc123\"" },
      "body": { "orderId": "12345", "status": "Confirmed" }
    },
    {
      "statusCode": 422,
      "resource": "/api/salesorders/12345/items/0",
      "body": {
        "field": "quantity",
        "message": "Quantity must be greater than 0"
      }
    }
  ]
}

Asynchronous Message-Based Integration

When latency is acceptable and system availability is key—such as updating inventory after a warehouse receipt—message queues are used. The ERP publishes events to a message broker like Apache Kafka or RabbitMQ.

A InventoryUpdated event is published as a JSON message on a topic named inventory.events:

{
  "eventId": "evt-88765",
  "eventType": "InventoryUpdated",
  "timestamp": "2023-10-15T11:22:33Z",
  "data": {
    "productId": "PROD-001",
    "locationCode": "WH-01",
    "quantity": 100,
    "sourceSystem": "Warehouse Management System"
  }
}

Consumers (e.g., an e-commerce platform, a reporting service) subscribe to this topic and process messages in batches or individually. The message broker guarantees delivery through acknowledgments (acks) and retry mechanisms.

The message format includes metadata in the message headers:

Content-Type: application/json
X-Message-Id: evt-88765
X-Source-System: ERP
X-Event-Version: 1.1

Failure modes:

Batch File Integration

For high-volume, low-frequency data transfers—like nightly sales reports or monthly financial consolidations—batch file integrations dominate. The ERP generates a flat file (CSV, XML, or EDI) and places it in a shared directory or cloud storage.

A daily sales summary file is generated as sales-20231015.csv:

orderNumber,customerNumber,orderDate,productCode,quantity,unitPrice,totalAmount
SO-12345,CUST-10042,2023-10-15,PROD-001,5,29.99,149.95
SO-12346,CUST-10043,2023-10-15,PROD-002,3,15.50,46.50

The file is written to /exports/sales/daily/20231015.csv and then moved to a completed subdirectory. A separate process—running via systemctl—monitors the exports directory:

systemctl status erp-batch-import

# ● erp-batch-import.service - ERP Batch Import Daemon
#    Loaded: loaded (/etc/systemd/system/erp-batch-import.service; enabled; vendor preset: enabled)
#    Active: active (running) since Tue 2023-10-15 02:00:00 UTC; 1h 32min ago
#   Main PID: 1245 (importer)
#     Status: "Processing 48 files in queue"
#      Tasks: 2 (limit: 4915)
#     Memory: 145.2M
#     CGroup: /system.slice/erp-batch-import.service
#             └─1245 /usr/bin/erp-importer --config /etc/erp-import.conf

The erp-import.conf file defines:

[batch]
directory = "/exports/sales/daily"
completed = "/exports/sales/completed"
error = "/exports/sales/errors"
format = "csv"
delimiter = ","
encoding = "utf-8"
schema = [
  { field = "orderNumber", type = "string", required = true },
  { field = "customerNumber", type = "string", required = true },
  { field = "orderDate", type = "date", format = "yyyy-MM-dd" },
  { field = "productCode", type = "string" },
  { field = "quantity", type = "integer", min = 1 },
  { field = "unitPrice", type = "decimal", scale = 2 },
  { field = "totalAmount", type = "decimal", scale = 2 }
]

[logging]
level = "info"
file = "/var/log/erp-importer.log"
max-size = "100MB"
max-backups = 10

Failures in batch processing:

  ERROR: Failed to parse row 123 in sales-20231015.csv
  Field: unitPrice
  Value: "29,99"
  Expected: decimal
  Error: Invalid decimal format: expected '12.34', got '12,34'

Event-Driven Architecture with Change Data Capture

When the ERP must propagate changes in real time—such as when a purchase order is approved—the architecture leverages change data capture (CDC). The ERP logs changes to a database table (ChangeLog) and triggers events based on insertions, updates, or deletions.

A PurchaseOrder is updated:

INSERT INTO ChangeLog (entity, entityId, operation, timestamp, data)
VALUES (
  'PurchaseOrder',
  'PO-2023-1001',
  'UPDATE',
  '2023-10-15 13:45:12',
  '{"status": "Approved", "approver": "[email protected]"}'
);

A service listens for new entries in ChangeLog and emits events via a message broker. The event is consumed by downstream systems.

The service uses a cursor-based CDC approach:

SELECT id, entity, entityId, operation, data, timestamp
FROM ChangeLog
WHERE id > ?lastProcessedId
ORDER BY id
LIMIT 1000;

The service stores the last processed id in a cdc_checkpoint table:

CREATE TABLE cdc_checkpoint (
  source_table VARCHAR(64) PRIMARY KEY,
  last_id BIGINT NOT NULL,
  last_timestamp TIMESTAMP WITH TIME ZONE
);

A failure occurs when the service crashes after processing 10,000 events but before updating the checkpoint. On restart, it resumes from last_id, avoiding duplicate processing.

Integration with External Systems via Webhooks

When a system outside the ERP needs to react to events—such as a CRM updating a customer record—the ERP exposes webhooks. A client registers a URL to receive notifications:

POST /api/webhooks
{
  "topic": "customer.updated",
  "url": "https://crm.example.com/webhook/customer-updated",
  "events": [
    "customer.created",
    "customer.updated",
    "customer.deleted"
  ],
  "filter": {
    "tenantId": "tenant-123"
  }
}

The ERP sends POST requests to the registered URL whenever a matching event occurs. The payload is a standard JSON object with metadata:

{
  "id": "webhook-78912",
  "topic": "customer.updated",
  "timestamp": "2023-10-15T14:00:00Z",
  "data": {
    "customerId": "CUST-10042",
    "name": "Acme Corp",
    "email": "[email protected]",
    "lastUpdatedBy": "[email protected]"
  },
  "context": {
    "tenantId": "tenant-123",
    "sourceSystem": "ERP"
  }
}

The webhook receiver must:

Error Handling and Monitoring

Integrations fail silently. A successful response does not imply correctness. The architecture must include:

  {
    "timestamp": "2023-10-15T14:01:02Z",
    "integration": "sales-order-to-erp",
    "step": "send-order",
    "status": "failed",
    "error": {
      "code": "ERP-503",
      "message": "ERP service unavailable",
      "details": "Connection timeout after 10 seconds"
    },
    "context": {
      "orderId": "SO-12345",
      "externalId": "ext-12345"
    }
  }
  CREATE TABLE integration_queue (
    id BIGSERIAL PRIMARY KEY,
    integration_name VARCHAR(128),
    payload JSONB,
    status VARCHAR(20) DEFAULT 'pending',
    attempts INT DEFAULT 0,
    next_retry TIMESTAMP WITH TIME ZONE,
    error_log TEXT,
    created_at TIMESTAMP WITH TIME ZONE
  );
  #!/bin/bash
  # check-integration-health
  set -euo pipefail

  for integration in sales-order-to-erp inventory-sync crm-customer-updates; do
    last_run=$(psql -c "SELECT max(created_at) FROM integration_queue WHERE integration_name = '$integration' AND status = 'completed'" -t)
    if [ -z "$last_run" ] || [ "$(date -d "$last_run" +%s)" -lt $(( $(date +%s) - 300 )) ]; then
      echo "WARNING: $integration has not completed in the last 5 minutes"
    fi
  done

Common Pitfalls and Sharp Edges

This architecture is not a set of tools but a set of expectations: reliable, observable, resilient, and predictable data flow between systems. It is the foundation upon which enterprise software operates.

This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.