
August 24, 2026
10 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
When a third-party payment gateway or SMS provider starts timing out, your entire application can grind to a halt as threads pile up waiting for responses that never come. Implementing circuit breakers and resilience patterns is the definitive way to isolate these failures and protect your core business logic from cascading collapse. In my experience building legal-tech portals and eCommerce systems where external integrations are critical, treating resilience as an architectural requirement rather than an afterthought is what separates stable production systems from fragile ones.
What Are Circuit Breakers and Resilience Patterns in Web Development?
Circuit breakers function exactly like electrical fuses: they monitor the flow of requests to a dependency and "trip" open when failures exceed a defined threshold, stopping all traffic to that dependency for a configured duration. This prevents your web server from exhausting its worker pool on hung connections. For developers working on Laravel API best practices, understanding this concept is mandatory because modern applications rarely exist in isolation; they depend on payment gateways like eSewa or Khalti, SMS providers, shipping APIs, and microservices.
The pattern consists of three distinct states that govern request flow:
- Closed: Normal operation. Requests pass through to the service. Failures are counted against a threshold.
- Open: The breaker has tripped. All requests fail immediately without attempting to contact the remote service. This returns a cached response or a graceful fallback.
- Half-Open: After a timeout period, the breaker allows a single test request through. If it succeeds, the circuit closes; if it fails, the timer resets.
Beyond the circuit breaker itself, resilience encompasses several complementary patterns. The Retry with Backoff pattern handles transient network glitches by retrying failed requests with exponentially increasing delays. The Bulkhead pattern isolates resource pools so a failure in one component (like an email queue) cannot starve another (like user authentication). The Timeout pattern ensures no single call can block indefinitely. Together, these form a defense-in-depth strategy essential for any production system integrating with Nepal's often-unreliable external infrastructure.
How Do You Implement a Circuit Breaker in Laravel 12?
Laravel 12 does not include a native circuit breaker in the framework core, but the ecosystem provides battle-tested solutions. My preferred approach for most client projects uses the spatie/laravel-circuit-breaker package because it integrates cleanly with Laravel's cache and configuration systems without requiring Redis-specific features. For teams already committed to Redis, redis-circuit-breaker offers atomic operations suitable for multi-server deployments.
Installation and Basic Configuration
composer require spatie/laravel-circuit-breaker Publish the configuration file to customize thresholds per service:
php artisan vendor:publish --tag="circuit-breaker-config" In config/circuit-breaker.php, define separate circuits for each external dependency. A common mistake I see on real client projects is using a single global circuit for all HTTP calls. This means if the SMS gateway fails, your payment processing also gets blocked. Always isolate circuits by service:
<?php
// config/circuit-breaker.php
return [
'circuits' => [
'esewa_payment' => [
'threshold' => 5, // Open after 5 failures
'timeout' => 60, // Stay open for 60 seconds
'half_open_max_calls' => 1, // Test with single call
'store' => 'redis', // Use Redis for shared state
],
'sms_gateway' => [
'threshold' => 10,
'timeout' => 120,
'half_open_max_calls' => 1,
'store' => 'file', // File store OK for single-server
],
],
]; Wrapping Service Calls
Apply the circuit breaker at the service layer, not in controllers. This keeps resilience logic encapsulated and testable. When building REST APIs in Laravel, this separation becomes critical because the same service may be called from HTTP endpoints, queued jobs, and CLI commands:
<?php
namespace App\Services\Payment;
use Spatie\CircuitBreaker\Facades\CircuitBreaker;
use App\Exceptions\PaymentServiceUnavailableException;
class EsewaPaymentService
{
public function processPayment(float $amount, string $reference): array
{
return CircuitBreaker::execute('esewa_payment', function () use ($amount, $reference) {
$response = Http::timeout(10)
->withHeaders(['Authorization' => config('services.esewa.key')])
->post('https://api.esewa.com.np/v2/payments', [
'amount' => $amount,
'reference_id' => $reference,
]);
if ($response->failed()) {
throw new \RuntimeException('eSewa payment failed');
}
return $response->json();
}, function () use ($reference) {
// Fallback: log for manual reconciliation
logger()->warning('eSewa circuit open', ['ref' => $reference]);
throw new PaymentServiceUnavailableException(
'Payment processing temporarily unavailable. Please try again later.'
);
});
}
} The fallback closure is where many implementations fail. Never return null silently from a fallback unless the caller explicitly handles it. Either return a meaningful default value, serve cached data, or throw a domain-specific exception that the UI can translate into a helpful message for users. On legal-tech portals I've built, we typically queue failed operations for retry while informing the user their request was received but is pending confirmation.
When Should You Use Retry Logic vs Circuit Breakers?
A frequent question from developers new to resilience engineering is whether to use retries, circuit breakers, or both. The answer depends entirely on the failure mode you're addressing. Understanding this distinction prevents misapplication that can make systems less reliable rather than more.
| Pattern | Best For | Failure Type | Risk If Misused |
|---|---|---|---|
| Retry + Backoff | Transient network errors, temporary rate limits | Short-lived, self-resolving | Amplifies load during outages |
| Circuit Breaker | Sustained outages, dependency failures | Prolonged, requires intervention | Blocks valid requests too early |
| Combined | Production external integrations | Mixed transient + sustained | Complexity increases debugging difficulty |
| Timeout Only | Non-critical background tasks | Slow responses acceptable | Wastes resources on doomed calls |
The correct combination for most payment and notification services in Nepal is retry-first with circuit-breaker-as-backstop. Attempt 2-3 retries with exponential backoff for transient issues. If those retries exhaust without success, the circuit breaker counts it as one failure event. This prevents a single slow outage from triggering the breaker prematurely while still protecting against sustained downtime.
In practice, configure Laravel's built-in retry helper alongside your circuit breaker. The key insight is that the circuit breaker wraps the entire retry sequence, not individual attempts:
CircuitBreaker::execute('sms_gateway', function () {
return retry(
times: 3,
callback: fn() => SmsGateway::send($message),
sleepMilliseconds: 1000,
when: fn(\Exception $e) => $e instanceof TransientNetworkException,
);
}, fallback: fn() => QueueFailedSms::dispatch($message)); How Does the Bulkhead Pattern Prevent Resource Exhaustion?
Circuit breakers protect against bad responses, but they don't prevent resource starvation. If your application shares a single connection pool or thread pool across all services, a slow-but-not-failing dependency can consume every available slot, blocking healthy services. The bulkhead pattern solves this by partitioning resources.
For Laravel applications running on PHP-FPM, bulkheads manifest as separate queue workers and connection limits rather than thread pools. On a recent eCommerce project handling high-volume order processing, we separated payment verification, inventory updates, and notification sending into dedicated queues with independent worker counts. This ensured that a slowdown in the notification provider couldn't prevent orders from being confirmed.
# .env configuration for bulkhead isolation
QUEUE_CONNECTION=redis
# Dedicated queues with separate supervisor processes
PAYMENT_QUEUE_CONNECTION=redis-payment
INVENTORY_QUEUE_CONNECTION=redis-inventory
NOTIFICATION_QUEUE_CONNECTION=redis-notification Configure Supervisor to allocate workers proportionally to business priority. Payment verification gets more workers than notifications because payment failures directly impact revenue. This is operational resilience that no amount of application-level circuit breaking can replace.
What Monitoring Is Required for Circuit Breakers in Production?
Deploying circuit breakers without observability is dangerous. You need to know when circuits trip, how long they stay open, and whether fallbacks are being triggered frequently enough to indicate a systemic problem. Without metrics, you're flying blind and may discover issues only when customers complain.
Instrument your circuit breakers to emit events to your logging or monitoring stack. Spatie's package fires events you can listen to:
// app/Providers/EventServiceProvider.php
protected $listen = [
\Spatie\CircuitBreaker\Events\CircuitOpened::class => [
\App\Listeners\LogCircuitOpened::class,
],
\Spatie\CircuitBreaker\Events\CircuitClosed::class => [
\App\Listeners\LogCircuitRecovered::class,
],
]; For teams using Laravel Telescope or Sentry, integrate circuit state into your existing dashboards. Track these key metrics:
- Trip frequency: How often does each circuit open? Frequent trips indicate chronic instability.
- Recovery time: How long do circuits stay open? Longer durations suggest the timeout may be too conservative or the upstream issue persists.
- Fallback invocation rate: High fallback usage degrades user experience even if the system stays up.
- False positive rate: Are circuits tripping during normal variance? Your threshold may be too aggressive.
Set alerts on trip events, not just on complete outages. A circuit tripping is a leading indicator; waiting for total failure means you've already lost the benefit of the pattern. For Nepal-based teams managing infrastructure remotely, proactive alerting via SMS or Slack is essential since you may not have real-time dashboard access during power or connectivity interruptions.
Common Mistakes When Implementing Resilience Patterns
After years of debugging resilience implementations in production, certain anti-patterns recur consistently. Avoiding these saves significant troubleshooting time:
Setting thresholds too low. A threshold of 2-3 failures will trip during normal network jitter. Start with 5-10 failures within a rolling window and adjust based on observed behavior. Legal-tech applications processing government API calls often need higher thresholds due to inherent upstream instability.
Ignoring idempotency. Retrying non-idempotent operations causes duplicate payments, double-sent emails, or corrupted data. Before adding retry logic to any payment integration, verify the endpoint supports idempotency keys or design your own deduplication layer.
Shared cache stores across environments. Using the same Redis instance for development, staging, and production circuit state means a test-induced trip in staging blocks production traffic. Namespace your circuit breaker cache keys by environment.
No fallback degradation path. Returning generic errors when circuits open wastes the pattern's purpose. Cache read-only data, queue write operations for later, or offer partial functionality. Users prefer "your order is confirmed, tracking will update shortly" over "service unavailable."
Testing only the happy path. Write integration tests that simulate circuit-open conditions. Verify fallbacks execute correctly and that the system recovers when the upstream service returns. Chaos engineering principles apply even at the application level.
Building Resilient Systems That Survive Real-World Conditions
Circuit breakers and resilience patterns are not optional luxuries for high-scale systems; they are fundamental requirements for any application depending on external services. In Nepal's infrastructure context, where power fluctuations, ISP instability, and third-party API reliability vary significantly, these patterns directly determine whether your application serves users or generates support tickets.
Start small: add circuit breakers to your most critical external integrations first. Instrument them properly from day one. Tune thresholds based on real traffic, not theoretical models. Combine with retries, bulkheads, and timeouts for layered defense. Most importantly, treat resilience as a continuous practice, not a one-time implementation. Review your circuit breaker metrics weekly, adjust configurations as dependencies change, and document your fallback behaviors so the next developer understands the trade-offs you made.
If you're building systems where uptime matters and need practical guidance on implementing these patterns in your specific stack, reach out to discuss your architecture. Whether you're integrating local payment gateways, building legal-tech platforms, or scaling eCommerce operations, getting resilience right from the start prevents costly rewrites and customer trust erosion down the line.

