McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Chaos Testing Payment Failure Paths
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Chaos Testing Payment Failure Paths

Inject failures into your payment flow to verify graceful degradation: (1) mock Paystack API returning 500 → your checkout endpoint should return a user-friendly error, not expose the API error; (2) mock webhook arriving with database unavailable → should queue the event for retry, not lose it; (3) inject a 30-second delay in verify call → should timeout and retry with backoff; (4) deliver the same webhook twice → idempotency should prevent double fulfillment.

In this article

  1. 1. Chaos Scenarios and Expected Behavior
  2. 2. Simulating Failures in Tests
  3. 3. Learn More

Chaos Scenarios and Expected Behavior

Failure ScenarioCorrect System BehaviorBad Behavior to Avoid
Paystack API returns 500 on initialize Return friendly error to user, log the raw error, allow retry Expose raw "Internal Server Error" from Paystack to user
Webhook arrives but database is unavailable Return 500 to Paystack (triggers retry), queue event for processing Return 200 and silently drop the event
Same webhook delivered twice First delivery fulfills; second is a no-op (idempotency) Double fulfillment or duplicate order credit
Paystack verify call times out Retry with exponential backoff (3 attempts), fail gracefully after User sees payment failed even though Paystack succeeded
Webhook signature check fails (network glitch corrupted header) Return 401, log the failure — do not process Process the event anyway or crash with unhandled error

Simulating Failures in Tests

// Simulate Paystack API outage
describe('Paystack API 500 error', () => {
  it('returns friendly error when Paystack is down', async () => {
    global.fetch = jest.fn().mockResolvedValue({
      ok: false,
      status: 500,
      json: () => Promise.resolve({ status: false, message: 'Internal server error' }),
    });

    var res = await request(app).post('/api/checkout').send({ email: 'user@test.com', amount: 10000 });
    expect(res.status).toBe(503);
    expect(res.body.error).toBe('Payment service temporarily unavailable');
    // Should NOT expose: "Paystack: Internal server error"
  });
});

// Simulate DB failure during webhook
describe('Database failure during webhook', () => {
  it('returns 500 so Paystack retries', async () => {
    jest.spyOn(db.orders, 'update').mockRejectedValue(new Error('Connection refused'));

    var payload = { event: 'charge.success', data: { reference: 'ref_001', metadata: { order_id: 1 } } };
    var body = JSON.stringify(payload);
    var sig = crypto.createHmac('sha512', process.env.PAYSTACK_SECRET_KEY).update(body).digest('hex');

    var res = await request(app)
      .post('/webhook/paystack')
      .set('x-paystack-signature', sig)
      .send(body);

    // Must return 500 so Paystack retries — not 200
    expect(res.status).toBe(500);
  });
});

// Simulate duplicate webhook delivery
describe('Idempotency under duplicate delivery', () => {
  it('fulfills order exactly once for duplicate events', async () => {
    var fulfillSpy = jest.spyOn(orderService, 'fulfill');
    var payload = JSON.stringify({ event: 'charge.success', data: { reference: 'ref_dup', metadata: { order_id: 99 } } });
    var sig = crypto.createHmac('sha512', process.env.PAYSTACK_SECRET_KEY).update(payload).digest('hex');
    var headers = { 'x-paystack-signature': sig, 'Content-Type': 'application/json' };

    await request(app).post('/webhook/paystack').set(headers).send(payload).expect(200);
    await request(app).post('/webhook/paystack').set(headers).send(payload).expect(200);

    expect(fulfillSpy).toHaveBeenCalledTimes(1); // Not twice
  });
});

Get weekly developer tips

Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.

No spam. Unsubscribe anytime.

Learn More

This guide is part of the Paystack testing guide.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Test Paystack API 500 errors: mock a 500 response from POST /transaction/initialize and verify your endpoint returns a friendly error message.
  • ✓Test database write failures during webhook handling: mock a DB error and verify the webhook returns 500 (so Paystack retries) rather than 200.
  • ✓Test webhook idempotency under chaos: send the same webhook event 3 times rapidly and verify the order is fulfilled exactly once.
  • ✓Test API timeout handling: add artificial 30-second delays and verify your timeout + retry logic fires correctly.
  • ✓Test the Paystack API being down for 30 minutes: does your checkout degrade gracefully, or do users see a 500?
  • ✓Chaos tests often reveal missing retry logic, missing idempotency, and missing error boundaries in payment code.

Frequently Asked Questions

What is the most important chaos scenario to test for a Paystack integration?
Duplicate webhook delivery is the highest-priority chaos scenario because it happens in the real world — Paystack retries webhooks on non-200 responses and occasionally delivers events more than once. If your handler is not idempotent, duplicate delivery causes real financial damage (double fulfillment, double credits). Test this first.
What happens to webhooks when Paystack cannot reach my server?
Paystack retries webhook delivery on 5xx responses or connection failures. They have a retry schedule with increasing delays. If your server is down for an extended period, some events may be permanently lost after the retry limit. This is why your reconciliation job (nightly verify scan) is important — it catches events that were never delivered.
Should webhook handlers return 500 when a DB write fails?
Yes. Returning 500 tells Paystack the event was not processed, triggering a retry. This is the correct behavior. Never return 200 when you failed to process the event — that tells Paystack to stop retrying, and you have lost the event permanently. Log the failure for investigation.
How do I simulate a Paystack API timeout in tests?
Mock the fetch call to delay for longer than your timeout: global.fetch = jest.fn().mockImplementation(() => new Promise(resolve => setTimeout(resolve, 35000))). Set your timeout to 30 seconds and verify the fetch is aborted and the retry logic fires. Use fake timers (jest.useFakeTimers()) to advance time without waiting.

Learn Paystack Integration

KES 2,999

The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.

Start Paystack Course

Not ready? Create Free Account

Keep reading

Testing Paystack Integrations: The Complete Guide

A complete guide to testing Paystack integrations at every level: test mode, test cards, unit tests with mocks, integration tests, end-to-end flows with Playwright, webhook simulation, contract testing, CI pipelines, staging environments, load testing, and coverage targets for payment code.

Read more

Simulating Declined Cards on Paystack

A card decline is one of the most common failure states in a payment flow, and your application must handle it gracefully. This guide shows how to simulate specific card declines in Paystack test mode and how to verify your decline handling code works correctly.

Read more

Mocking the Paystack API in PHPUnit

PHPUnit tests for Paystack integrations in Laravel or PHP can run without any network calls using Laravel's Http::fake() or Mockery. This guide shows how to mock Paystack responses, test webhook handlers, and verify your payment logic in isolation.

Read more

Contract Testing Your Paystack Webhook Handler

Contract testing verifies that your webhook handler correctly handles the exact payload structure Paystack sends. If Paystack adds or renames a field, contract tests catch the mismatch before it reaches production. This guide shows how to write contract tests for Paystack webhook events.

Read more

Paystack CORS Errors and What They Really Mean

A CORS error on Paystack means you are calling the Paystack API directly from your frontend JavaScript. The fix is not to add CORS headers anywhere. The fix is to move the API call to your server.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.