Bonaventure OgetoBy Bonaventure Ogeto|

Load Testing a Payment Endpoint Responsibly

To load test your payment endpoint responsibly: mock the Paystack API call at your application boundary (return a fake authorization_url without calling Paystack), then run your load test against your server. This measures your server's capacity to handle concurrent checkouts without abusing the Paystack API. For webhook handler load testing, generate valid HMAC-signed webhook payloads and send them at high concurrency to your webhook endpoint.

The Responsible Load Testing Approach

Your payment flow has two external dependencies: the Paystack API and your database. Load tests should measure your system, not stress Paystack's infrastructure.

For checkout initiation load tests:

  1. Add a test mode flag in your checkout endpoint that returns a mocked authorization_url without calling Paystack
  2. Or use a feature flag that swaps the Paystack client for a stub that returns fixed responses
  3. Run k6 or Artillery against this endpoint — you are testing your server's throughput, DB connection pooling, and response time

For webhook handler load tests:

  1. Pre-generate 10,000 valid HMAC-signed webhook payloads (charge.success events)
  2. Send them concurrently to your webhook endpoint
  3. Measure DB write throughput, connection pool, and queue depth

k6 Load Test Script for Paystack Checkout

// load-tests/checkout.js
// Run: k6 run load-tests/checkout.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

var errorRate = new Rate('errors');

export var options = {
  stages: [
    { duration: '30s', target: 10 },  // Ramp up to 10 concurrent users
    { duration: '60s', target: 50 },  // Ramp up to 50 concurrent users
    { duration: '30s', target: 0 },   // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(99)<500'], // 99% of requests under 500ms
    errors: ['rate<0.01'],            // Less than 1% error rate
  },
};

export default function () {
  var payload = JSON.stringify({
    email: 'loadtest+' + __VU + '@example.com',
    amount: 10000,
    order_id: Math.floor(Math.random() * 1000000),
  });

  var res = http.post('https://staging.yourapp.com/api/checkout', payload, {
    headers: {
      'Content-Type': 'application/json',
      // Include auth if needed
    },
  });

  check(res, {
    'status is 200': (r) => r.status === 200,
    'has authorization_url': (r) => r.json('authorization_url') !== undefined,
  });

  errorRate.add(res.status !== 200);
  sleep(1);
}

Ensure your staging checkout endpoint is configured to mock the Paystack API call during load tests (use PAYSTACK_MOCK=true environment variable).

Learn More

Key Takeaways

  • Never run load tests against the real Paystack API — use a mock or stub at the application boundary.
  • Load test your server's ability to handle concurrent checkout requests, not Paystack's infrastructure.
  • Use k6 or Artillery to send concurrent requests to your checkout endpoint with mocked Paystack responses.
  • Load test your webhook handler: generate valid HMAC-signed payloads and send 100+ concurrent webhook events.
  • Measure: response time p99, error rate, database connection pool exhaustion, and memory usage under load.
  • A payment endpoint should handle your expected peak load + 3x headroom before performance degrades.

Frequently Asked Questions

Can I run load tests against the Paystack test API?
Technically yes, but do not do this without contacting Paystack first. Sending thousands of requests to api.paystack.co — even in test mode — is traffic that consumes their infrastructure. It may trigger rate limiting, block your account, or impact other Paystack customers. Load test your own system, not Paystack's.
What is a realistic target for checkout endpoint response time?
p99 response time under 500ms is a good target for the checkout initiation endpoint (which calls POST /transaction/initialize). Since that call is external (100-200ms just for the Paystack round trip), your server should add less than 300ms of processing time. Under real load, p99 under 1000ms is acceptable if you cannot improve the Paystack call latency.
How many concurrent users can a typical Node.js server handle for payment checkouts?
This depends heavily on database query time, connection pool size, and Paystack API latency. A single Node.js server with a 20-connection PostgreSQL pool typically handles 50-100 concurrent payment checkouts before degrading, if each request takes ~200ms. Horizontal scaling (multiple instances behind a load balancer) is the solution for higher volumes.
Should I load test the webhook endpoint?
Yes — especially if you expect high sales volume (e.g., flash sales). Generate pre-signed webhook payloads and send them at 10x your expected peak rate. Verify that database writes are idempotent, connection pool is not exhausted, and p99 response time stays under 200ms (Paystack expects fast webhook responses to avoid retries).

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