How to Design Reliable Integrations Between Business Systems
A defensive engineering guide to integrating business systems: idempotency keys, circuit breakers, outbox patterns, and error recovery.
Integrating business systems—such as ERPs (Fortnox, Visma, SAP), payment gateways (Stripe, Adyen), CRM platforms, and logistics providers—is where software systems most frequently encounter unexpected operational failures.
Third-party APIs experience intermittent network partitions, transient 502 Bad Gateway responses, schema drifts, and undocumented rate limits. A robust integration architecture assumes external networks will fail constantly and implements defensive engineering patterns to guarantee system integrity.
+-------------------------------------------------------------------------------+
| TRANSACTIONAL OUTBOX PATTERN |
+-------------------------------------------------------------------------------+
| 1. DATABASE TRANSACTION: |
| - Mutate Order State -> 'paid' |
| - Insert Outbox Record -> 'sync_to_erp' (Same ACID Transaction) |
+-------------------------------------------------------------------------------+
|
v
+-------------------------------------------------------------------------------+
| ASYNCHRONOUS INTEGRATION WORKER |
| 2. Poll Outbox Record / Queue -> Read ERP Credentials |
| 3. Check Circuit Breaker State (Open / Closed / Half-Open) |
| 4. Execute HTTP Request with Idempotency-Key Header |
| 5. On 200 OK -> Mark Outbox Record 'delivered' |
| 6. On 5xx Error -> Exponential Backoff Retry (Max 5 attempts) |
| 7. On Exhaustion -> Route to Dead-Letter Queue (DLQ) & Alert On-Call |
+-------------------------------------------------------------------------------+
The Four Mandatory Integration Patterns#
1. The Transactional Outbox Pattern#
The most dangerous integration bug is the Dual-Write Problem: updating your local database and immediately making an HTTP call to an external API within the same web controller. If the HTTP call hangs or crashes, your database state and external ERP state diverge irreversibly.
Instead, persist the outbound integration payload into an outbox database table within the same atomic ACID transaction as your domain mutation. A dedicated background worker asynchronously picks up pending outbox entries and dispatches them with retry guarantees.
2. Idempotency Keys on All Write Operations#
Network requests can fail ambiguously: a client sends an HTTP POST /charges, the server processes the charge successfully, but the network connection drops before the HTTP 200 response reaches the client. If the client blindly retries, the customer is charged twice.
To prevent duplicate mutations, all write operations must transmit a unique Idempotency-Key header (e.g. UUID v4 or deterministic hash):
// Dispatching idempotent payment capture
$response = Http::withHeaders([
'Idempotency-Key' => 'capture_order_' . $order->uuid . '_' . $order->updated_at->timestamp,
'Authorization' => 'Bearer ' . $apiKey,
])->post('https://api.paymentgateway.com/v1/charges', [
'amount' => $order->total_cents,
'currency' => 'EUR',
]);
3. Circuit Breakers for Downstream Protection#
When an external partner API goes down, sending thousands of consecutive HTTP requests wastes server threads, exhausts connection pools, and degrades local performance.
A Circuit Breaker monitors downstream error rates:
- Closed (Normal): Requests pass through to the partner API.
- Open (Tripped): After 5 consecutive 5xx failures within 30 seconds, the circuit opens. All subsequent requests fail immediately locally without making external network calls, falling back to a buffered queue.
- Half-Open (Testing): After a reset timeout (e.g. 60 seconds), a single canary request tests if the partner API has recovered.
4. Dead-Letter Queues (DLQ) and Self-Service Replay#
When a payload repeatedly fails due to unrecoverable validation errors (e.g., an invalid VAT number rejected by a tax authority), the job is sequestered into a Dead-Letter Queue. In Alegor's Integrations Module, developers and support personnel can inspect payload details, correct the customer record, and trigger a one-click manual replay.
// Managing dead letter queue recovery in Alegor
class DeadLetterQueueService
{
public function retryFailedDelivery(string $deliveryUuid): DeliveryResult
{
$delivery = WebhookDelivery::where('uuid', $deliveryUuid)->firstOrFail();
// Log manual operator intervention in immutable audit log
Audit::log('integration.dlq_retry', $delivery);
return $this->dispatcher->dispatchDirectly($delivery->endpoint, $delivery->payload);
}
}
Learn how the Alegor Platform structures its Integrations Module or read about Designing Software That Can Evolve for Ten Years.
Building a business-critical system?
Evaluate how Alegor can serve as your foundation.
Related Engineering Knowledge
What Makes Software Business-Critical?
Defining business-critical software: high operational stakes, zero data-loss tolerance, audit compliance, and engineering resilience.
Webhooks vs Polling vs Event-Driven Integrations
A comprehensive technical comparison of data synchronization patterns: HTTP polling, outbound webhooks, and real-time event streaming architectures.
API-First Architecture for Business Systems
Why designing clean, versioned APIs before user interfaces enables headless integrations, third-party ecosystems, and multi-client longevity.