Skip to main content
The Paystack SDK uses a unique error handling pattern that combines TypeScript’s type safety with Zod’s runtime validation. Unlike traditional try-catch approaches, methods return a result object that contains either data or an error.

The Result Pattern

All SDK methods return a result object with two properties:
type Result<T> = {
  data: T | undefined;
  error: ZodError | undefined;
}
Either data or error will be defined, never both. This pattern eliminates the need for try-catch blocks in most cases.

Basic Error Handling

Here’s how to handle responses from the SDK:
import { Paystack } from '@efobi/paystack';

const paystack = new Paystack(process.env.PAYSTACK_SECRET_KEY!);

const result = await paystack.transaction.verify('invalid_reference');

if (result.error) {
  // Handle validation or API error
  console.error('Error:', result.error.message);
  console.error('Issues:', result.error.issues);
} else if (result.data) {
  // Success - use the data
  console.log('Transaction:', result.data.data);
  console.log('Status:', result.data.status);
}

Types of Errors

The SDK handles three main categories of errors:

Validation Errors

Input data fails Zod schema validation before the API request

API Errors

Paystack API returns an error response (4xx, 5xx status codes)

Network Errors

Connection issues, timeouts, or network failures

1. Validation Errors

Zod validates all inputs before making API requests. If validation fails, you’ll receive a detailed error:
const result = await paystack.transaction.initialize({
  email: 'invalid-email',  // ❌ Not a valid email
  amount: '50000'
});

if (result.error) {
  console.log(result.error.issues);
  // [
  //   {
  //     code: 'invalid_string',
  //     validation: 'email',
  //     path: ['email'],
  //     message: 'Invalid email'
  //   }
  // ]
}

Validation Error Structure

Zod errors provide detailed information:
interface ZodIssue {
  code: string;           // Error type (e.g., 'invalid_string', 'too_small')
  path: (string | number)[]; // Path to the invalid field
  message: string;        // Human-readable error message
  validation?: string;    // Validation type (e.g., 'email', 'url')
  expected?: string;      // Expected value/type
  received?: string;      // Actual value/type received
}
// Get user-friendly error messages
const result = await paystack.transaction.initialize(invalidData);

if (result.error) {
  const flatErrors = result.error.flatten();
  console.log(flatErrors.fieldErrors);
  // {
  //   email: ['Invalid email'],
  //   amount: ['Required']
  // }
}

2. API Errors

When Paystack returns an error response, the SDK parses it and returns the error in the result. Here’s the implementation from src/main/transaction.ts:119-124:
src/main/transaction.ts
if (!response.ok) {
  const { data, error } = await genericResponse.safeParseAsync(raw);
  return { data, error };
}
const { data, error } = await txnInitializeSuccess.safeParseAsync(raw);
return { data, error };

Common API Errors

The request contains invalid parameters.
const result = await paystack.transaction.initialize({
  email: 'customer@example.com',
  amount: '-1000'  // ❌ Negative amount
});

if (result.error) {
  // API validation failed
  console.error('Bad request:', result.error.message);
}
Invalid or missing API key.
// Using invalid key
const paystack = new Paystack('sk_test_invalid_key');
const result = await paystack.transaction.list();

if (result.error) {
  // Unauthorized - check your API key
  console.error('Authentication failed');
}
The requested resource doesn’t exist.
const result = await paystack.transaction.verify('non_existent_ref');

if (result.error) {
  // Transaction not found
  console.error('Resource not found');
}
Rate limit exceeded.
const result = await paystack.transaction.list();

if (result.error) {
  // Too many requests - implement backoff
  console.error('Rate limited - retry after delay');
}
Paystack server error (rare).
const result = await paystack.transaction.initialize(data);

if (result.error) {
  // Server error - retry or contact support
  console.error('Server error - please try again');
}

3. Network Errors

Network errors occur outside the SDK’s control and should be caught with try-catch:
try {
  const result = await paystack.transaction.verify(reference);
  
  if (result.error) {
    // Validation or API error
    handleApiError(result.error);
  } else if (result.data) {
    // Success
    processTransaction(result.data);
  }
} catch (error) {
  // Network error: connection timeout, DNS failure, etc.
  console.error('Network error:', error.message);
  // Implement retry logic or show user-friendly message
}

Error Handling Patterns

Pattern 1: Simple Check

For basic operations where you just need to know if it succeeded:
const result = await paystack.transaction.initialize({
  email: 'customer@example.com',
  amount: '50000'
});

if (result.error) {
  return { success: false, message: 'Failed to initialize transaction' };
}

return { success: true, url: result.data.data.authorization_url };

Pattern 2: Detailed Error Handling

When you need granular control:
const result = await paystack.transaction.verify(reference);

if (result.error) {
  // Log full error for debugging
  console.error('Verification failed:', result.error);
  
  // Extract user-friendly messages
  const errorMessages = result.error.issues.map(issue => ({
    field: issue.path.join('.'),
    message: issue.message
  }));
  
  return {
    success: false,
    errors: errorMessages
  };
}

// Success path
const transaction = result.data.data;
return {
  success: true,
  status: transaction.status,
  amount: transaction.amount
};

Pattern 3: Type Guards

Use TypeScript type guards for safer code:
function isError<T>(result: { data: T | undefined; error: any }): result is { error: any } {
  return result.error !== undefined;
}

const result = await paystack.transaction.initialize(data);

if (isError(result)) {
  // TypeScript knows result.error exists
  throw new Error(`Transaction failed: ${result.error.message}`);
}

// TypeScript knows result.data exists here
const { authorization_url } = result.data.data;

Pattern 4: Centralized Error Handler

Create a reusable error handler:
function handlePaystackError(error: any) {
  const issues = error.issues || [];
  
  return {
    message: 'Operation failed',
    errors: issues.map((issue: any) => ({
      field: issue.path.join('.') || 'general',
      message: issue.message,
      code: issue.code
    }))
  };
}

// Usage
const result = await paystack.transaction.list();

if (result.error) {
  const formatted = handlePaystackError(result.error);
  return res.status(400).json(formatted);
}

return res.json(result.data);

Framework Integration

import express from 'express';
import { Paystack } from '@efobi/paystack';

const app = express();
const paystack = new Paystack(process.env.PAYSTACK_SECRET_KEY!);

app.post('/api/transactions/verify/:reference', async (req, res) => {
  try {
    const result = await paystack.transaction.verify(req.params.reference);
    
    if (result.error) {
      return res.status(400).json({
        success: false,
        message: 'Verification failed',
        errors: result.error.flatten().fieldErrors
      });
    }
    
    return res.json({
      success: true,
      data: result.data.data
    });
  } catch (error) {
    console.error('Network error:', error);
    return res.status(500).json({
      success: false,
      message: 'Internal server error'
    });
  }
});

Best Practices

Always Check Errors First

Check for result.error before accessing result.data to avoid undefined errors.
// ✅ Good
if (result.error) return;
console.log(result.data);

// ❌ Bad
console.log(result.data); // May be undefined

Log Errors Completely

Log the full error object for debugging, but show user-friendly messages to users.
if (result.error) {
  console.error('Full error:', result.error);
  return 'Transaction failed. Please try again.';
}

Use Try-Catch for Network

Wrap SDK calls in try-catch to handle network errors.
try {
  const result = await paystack.transaction.list();
  // Handle result
} catch (error) {
  // Handle network error
}

Don't Swallow Errors

Always handle or propagate errors. Never silently ignore them.
// ❌ Bad
if (result.error) {
  // Do nothing
}

// ✅ Good
if (result.error) {
  throw new Error('Transaction failed');
}

Debugging Errors

The SDK logs API error responses to the console automatically. Check src/main/fetcher.ts:65-67:
src/main/fetcher.ts
if (!response.ok) {
  console.error("API Error Response:", raw);
}
This helps you debug issues during development.

Enable Detailed Logging

const result = await paystack.transaction.verify(reference);

if (result.error) {
  // Log everything for debugging
  console.log('Error name:', result.error.name);
  console.log('Error message:', result.error.message);
  console.log('Issues:', JSON.stringify(result.error.issues, null, 2));
  console.log('Formatted:', result.error.format());
  console.log('Flattened:', result.error.flatten());
}

Next Steps