Bonaventure OgetoBy Bonaventure Ogeto|

Gateway Failover: Designing for Provider Outages

Gateway failover architecture: 1) Health check endpoint — poll the primary gateway's health URL every 30 seconds. 2) Circuit breaker — after N consecutive failures, trip the breaker and route to secondary. 3) Payment provider abstraction layer — your checkout code calls an interface, not Paystack directly. 4) Secondary gateway — same interface implementation for a different provider. 5) Automatic recovery — re-check primary after 5 minutes; restore when healthy. What failover cannot solve: active subscriptions (tokens are gateway-specific), stored card authorizations, in-flight transactions when the outage started.

Circuit Breaker Implementation

class GatewayCircuitBreaker {
  constructor({ primaryGateway, fallbackGateway, failureThreshold = 5, resetTimeoutMs = 300000 }) {
    this.primary = primaryGateway;
    this.fallback = fallbackGateway;
    this.failures = 0;
    this.failureThreshold = failureThreshold;
    this.state = 'closed'; // closed = primary active, open = using fallback
    this.resetTimeout = resetTimeoutMs;
    this.lastFailureTime = null;

    // Health check every 30 seconds
    setInterval(() => this.healthCheck(), 30000);
  }

  async healthCheck() {
    if (this.state !== 'open') return;
    // Check if timeout has elapsed since last failure
    if (Date.now() - this.lastFailureTime > this.resetTimeout) {
      try {
        await this.primary.ping(); // implement a lightweight health check
        this.reset(); // primary healthy — close the breaker
        console.log('Circuit breaker reset: primary gateway restored');
      } catch {
        this.lastFailureTime = Date.now(); // extend timeout
      }
    }
  }

  async initializePayment(params) {
    var gateway = this.state === 'open' ? this.fallback : this.primary;

    try {
      var result = await gateway.initialize(params);
      if (this.state !== 'open') this.failures = 0; // reset on success
      return result;
    } catch (err) {
      if (this.state !== 'open') {
        this.failures++;
        this.lastFailureTime = Date.now();
        if (this.failures >= this.failureThreshold) {
          this.state = 'open';
          console.error('Circuit breaker opened: switching to fallback gateway');
        }
      }
      throw err; // re-throw so caller handles
    }
  }

  reset() {
    this.state = 'closed';
    this.failures = 0;
  }
}

// Usage
var payments = new GatewayCircuitBreaker({
  primaryGateway: new PaystackGateway(process.env.PAYSTACK_SECRET_KEY),
  fallbackGateway: new FlutterwaveGateway(process.env.FLUTTERWAVE_SECRET_KEY),
  failureThreshold: 5,
  resetTimeoutMs: 5 * 60 * 1000, // 5 minutes
});

Learn More

See building a payment provider abstraction layer for the prerequisite design that makes failover possible.

Sign up for the McTaba newsletter

Key Takeaways

  • Build the abstraction layer first — failover only works if your code is not directly coupled to one gateway.
  • Health check the primary gateway independently from transaction attempts — do not let failed transactions trip the breaker.
  • Circuit breaker: open on N failures, half-open after timeout, close when health check passes.
  • Failover only helps new transactions — it cannot recover in-flight transactions from before the outage.
  • Subscription tokens and stored card authorizations are not portable between gateways.

Frequently Asked Questions

How often does Paystack have outages that would trigger failover?
Paystack has historically been very reliable — major outages affecting checkout are rare, typically a few times per year and usually brief (under 1 hour). For most businesses, the engineering cost of building and maintaining a second gateway integration exceeds the revenue protected. Calculate: if Paystack is down for 2 hours/year, and you do NGN 10M/month, you lose ~NGN 28,000/year to outages. Is that worth a second integration that costs 2-3 weeks of engineering time to build and ongoing maintenance?
What does "health check" look like for Paystack specifically?
Paystack has a status page at status.paystack.com — you can programmatically check this. A lightweight approach: make a GET /bank call (list banks) — it is cheap, read-only, and exercises the Paystack API. If it returns 200, the API is up. If it fails or times out, the breaker records a failure. Do not use a transaction initialize for health checks — you would be creating real pending transactions.
How do I handle webhooks from two gateways at the same time?
Each gateway sends webhooks to its own configured URL. Use separate webhook endpoints: /webhooks/paystack and /webhooks/flutterwave. Each handler validates its own HMAC signature and processes events with its own field mapping. Store the source gateway in your order/transaction records so you know which gateway's verify endpoint to call when confirming payment.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon