Skip to content

Example API Debugging

Jean-Marc Strauven edited this page Aug 6, 2025 · 1 revision

API Integration Debugging Example

Learn how to use ChronoTrace to debug external API integrations, identify failures, optimize response times, and implement robust error handling.


🎯 Scenario: Payment Gateway Integration Issues

Your payment processing system is experiencing intermittent failures and slow response times, causing checkout failures and poor user experience.

The Problem

  • Payment API calls failing randomly (5-10% failure rate)
  • Slow payment processing (3-8 seconds)
  • Timeout errors during peak hours
  • Inconsistent error responses
  • Users getting charged but orders not completing

πŸ“‹ Step 1: Enable API Monitoring

Configure ChronoTrace to capture detailed HTTP events for payment integration:

# Target payment-related routes
CHRONOTRACE_MODE=targeted
// config/chronotrace.php
'targeted_routes' => [
    'checkout/*',
    'api/payments/*',
    'api/orders/*/payment',
    'webhooks/payment/*',
],

'http' => [
    'enabled' => true,
    'include_request_body' => true,
    'include_response_body' => true,
    'max_body_size' => 128 * 1024, // 128KB for payment data
    'timeout_threshold' => 5000,    // Flag requests >5s
],

πŸš€ Step 2: Capture Payment Failures

Let's generate some payment transactions to capture both successful and failed scenarios:

# Start recording payment API calls
php artisan chronotrace:record --routes="*payment*" --duration=1h --sample-rate=1.0

# Monitor in real-time
tail -f storage/logs/laravel.log | grep -i payment

πŸ” Step 3: Analyze Failed Payments

List traces to identify payment failures:

# Find failed payment attempts
php artisan chronotrace:list --route="*payment*" --status=error --limit=10

# Example output showing various failure types:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Trace ID   β”‚ Timestamp           β”‚ Method β”‚ Status   β”‚ Route                   β”‚ Duration β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ fail_001   β”‚ 2024-08-06 14:30:15 β”‚ POST   β”‚ 500      β”‚ api/payments/process    β”‚ 30,000ms β”‚
β”‚ fail_002   β”‚ 2024-08-06 14:32:42 β”‚ POST   β”‚ 422      β”‚ api/payments/process    β”‚ 156ms    β”‚
β”‚ fail_003   β”‚ 2024-08-06 14:35:18 β”‚ POST   β”‚ 500      β”‚ api/payments/process    β”‚ 8,456ms  β”‚
β”‚ timeout1   β”‚ 2024-08-06 14:37:25 β”‚ POST   β”‚ 504      β”‚ api/payments/process    β”‚ 30,000ms β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“Š Step 4: Deep Dive into Payment API Issues

Let's analyze a timeout failure in detail:

# Analyze timeout failure focusing on HTTP events
php artisan chronotrace:replay timeout1 --filter=http

Timeout Failure Analysis

β”Œβ”€ REQUEST INFORMATION ────────────────────────────────────────┐
β”‚ Trace ID: timeout1                                          β”‚
β”‚ Method: POST /api/payments/process                          β”‚
β”‚ Status: 504 Gateway Timeout                                 β”‚
β”‚ Duration: 30,000ms                                          β”‚
β”‚ Memory: 25MB                                                β”‚
β”‚ User: customer_789                                          β”‚
β”‚ Order ID: ORD-2024-08-06-12345                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€ HTTP EVENTS (4 requests) ───────────────────────────────────┐
β”‚ [+0ms] POST https://api.stripe.com/v1/payment_intents (30,000ms) ❌ TIMEOUT
β”‚        Request Headers:
β”‚          Authorization: Bearer sk_live_[REDACTED]
β”‚          Content-Type: application/x-www-form-urlencoded
β”‚          Idempotency-Key: idem_2024080614301512345
β”‚        Request Body:
β”‚          amount=29999&currency=usd&payment_method=pm_1234567890
β”‚          &confirm=true&return_url=https://example.com/return
β”‚        Response: TIMEOUT_ERROR
β”‚        Connection: timeout after 30.000s
β”‚
β”‚ [+30,100ms] POST https://api.stripe.com/v1/payment_intents (15,234ms) ⚠️ RETRY
β”‚            Request: Same as above (automatic retry)
β”‚            Response Status: 429 Too Many Requests
β”‚            Response Body: {"error": {"type": "rate_limit_error", 
β”‚                          "message": "Too many requests"}}
β”‚
β”‚ [+45,500ms] GET https://api.stripe.com/v1/payment_intents/pi_1234567890 (2,345ms)
β”‚            Request: GET payment status after timeout
β”‚            Response Status: 200 OK
β”‚            Response Body: {"status": "succeeded", "amount": 29999, 
β”‚                          "charges": {"data": [{"paid": true}]}}
β”‚            ⚠️ Payment actually succeeded but we didn't know!
β”‚
β”‚ [+48,000ms] POST https://webhook.site/test-endpoint (567ms)
β”‚            Request: Error notification webhook
β”‚            Response Status: 200 OK
β”‚            Note: Notifying about "failed" payment that actually succeeded
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”΄ Issues Identified

  1. Timeout Configuration: 30-second timeout too long for user experience
  2. No Retry Strategy: Automatic retry hitting rate limits
  3. Status Checking: No proper status verification after timeout
  4. Double Charging Risk: Payment succeeded but system thought it failed
  5. Poor Error Handling: Generic timeout response to user

πŸ› οΈ Step 5: Fix API Integration Issues

Issue 1: Implement Proper Timeout and Retry Strategy

Before (problematic code):

// PaymentService.php - Poor timeout handling
public function processPayment($paymentData)
{
    $response = Http::timeout(30) // Too long!
        ->post('https://api.stripe.com/v1/payment_intents', $paymentData);
    
    if ($response->failed()) {
        throw new PaymentException('Payment failed');
    }
    
    return $response->json();
}

After (optimized code):

// PaymentService.php - Proper timeout and retry strategy
public function processPayment($paymentData)
{
    return $this->withRetry(function () use ($paymentData) {
        return Http::timeout(10) // Shorter timeout
            ->retry(2, 1000, function ($exception, $request) {
                // Only retry on specific conditions
                return $exception instanceof ConnectException || 
                       $this->isRetryableError($exception);
            })
            ->withHeaders([
                'Idempotency-Key' => $this->generateIdempotencyKey($paymentData),
            ])
            ->post('https://api.stripe.com/v1/payment_intents', $paymentData);
    });
}

private function withRetry($callback, $maxAttempts = 3)
{
    $attempt = 1;
    
    while ($attempt <= $maxAttempts) {
        try {
            $response = $callback();
            
            if ($response->successful()) {
                return $response->json();
            }
            
            // Handle specific error cases
            if ($response->status() === 429) {
                // Rate limit - wait and retry
                sleep(pow(2, $attempt)); // Exponential backoff
                $attempt++;
                continue;
            }
            
            if ($this->isRetryableError($response)) {
                $attempt++;
                continue;
            }
            
            // Non-retryable error
            throw new PaymentException($response->json()['error']['message'] ?? 'Payment failed');
            
        } catch (ConnectException $e) {
            if ($attempt === $maxAttempts) {
                return $this->handleTimeout($paymentData);
            }
            $attempt++;
            sleep(1);
        }
    }
}

Issue 2: Implement Timeout Recovery

// Handle timeout scenarios properly
private function handleTimeout($paymentData)
{
    // Check if payment actually succeeded
    $paymentIntent = $this->checkPaymentStatus($paymentData['payment_intent_id']);
    
    if ($paymentIntent && $paymentIntent['status'] === 'succeeded') {
        Log::info('Payment succeeded despite timeout', [
            'payment_intent_id' => $paymentIntent['id'],
            'amount' => $paymentIntent['amount']
        ]);
        
        return $paymentIntent;
    }
    
    // Payment truly failed or is still processing
    throw new PaymentTimeoutException(
        'Payment processing timeout. Please check your payment status.',
        ['payment_intent_id' => $paymentData['payment_intent_id']]
    );
}

private function checkPaymentStatus($paymentIntentId)
{
    try {
        $response = Http::timeout(5)
            ->get("https://api.stripe.com/v1/payment_intents/{$paymentIntentId}");
            
        return $response->successful() ? $response->json() : null;
    } catch (Exception $e) {
        Log::error('Failed to check payment status', [
            'payment_intent_id' => $paymentIntentId,
            'error' => $e->getMessage()
        ]);
        return null;
    }
}

Issue 3: Add Circuit Breaker Pattern

// Prevent cascading failures
class PaymentCircuitBreaker
{
    private $failureThreshold = 5;
    private $timeoutSeconds = 60;
    
    public function execute($callback)
    {
        $failures = Cache::get('payment_failures', 0);
        $lastFailure = Cache::get('payment_last_failure');
        
        // Circuit is open - fast fail
        if ($failures >= $this->failureThreshold) {
            if ($lastFailure && now()->diffInSeconds($lastFailure) < $this->timeoutSeconds) {
                throw new CircuitBreakerOpenException('Payment service temporarily unavailable');
            }
            
            // Reset circuit breaker after timeout
            Cache::forget('payment_failures');
            Cache::forget('payment_last_failure');
        }
        
        try {
            $result = $callback();
            
            // Success - reset failure count
            Cache::forget('payment_failures');
            Cache::forget('payment_last_failure');
            
            return $result;
            
        } catch (Exception $e) {
            // Increment failure count
            Cache::put('payment_failures', $failures + 1, 300);
            Cache::put('payment_last_failure', now(), 300);
            
            throw $e;
        }
    }
}

Issue 4: Implement Async Payment Processing

// PaymentController.php - Async processing for better UX
public function processPayment(Request $request)
{
    $paymentData = $this->validatePaymentData($request);
    
    // Start async payment processing
    $job = ProcessPaymentJob::dispatch($paymentData)
        ->onQueue('payments')
        ->delay(now()->addSeconds(1));
    
    return response()->json([
        'status' => 'processing',
        'job_id' => $job->getJobId(),
        'polling_url' => route('payment.status', ['job' => $job->getJobId()]),
        'estimated_time' => 30 // seconds
    ]);
}

// ProcessPaymentJob.php
class ProcessPaymentJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    
    public function handle(PaymentService $paymentService)
    {
        try {
            $result = $paymentService->processPayment($this->paymentData);
            
            // Broadcast success
            broadcast(new PaymentProcessedEvent($this->paymentData['order_id'], $result));
            
        } catch (PaymentTimeoutException $e) {
            // Handle timeout - check status later
            CheckPaymentStatusJob::dispatch($this->paymentData)->delay(now()->addMinutes(2));
            
        } catch (Exception $e) {
            // Handle failure
            broadcast(new PaymentFailedEvent($this->paymentData['order_id'], $e->getMessage()));
            
            throw $e; // Re-throw to trigger retry mechanism
        }
    }
}

βœ… Step 6: Test the Improved Integration

After implementing fixes, let's test the improved payment system:

# Record new payment traces with fixes
php artisan chronotrace:record --routes="*payment*" --duration=30m

# Simulate various payment scenarios
php artisan payment:test --scenarios=success,timeout,rate-limit,network-error

Improved Performance Results

# Check improved payment processing
php artisan chronotrace:list --route="*payment*" --since="30 minutes ago"

# Results showing improvements:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Trace ID   β”‚ Timestamp           β”‚ Method β”‚ Status   β”‚ Route                   β”‚ Duration β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ good_001   β”‚ 2024-08-06 16:30:15 β”‚ POST   β”‚ 200      β”‚ api/payments/process    β”‚ 1,245ms  β”‚
β”‚ good_002   β”‚ 2024-08-06 16:32:42 β”‚ POST   β”‚ 200      β”‚ api/payments/process    β”‚ 2,156ms  β”‚
β”‚ timeout2   β”‚ 2024-08-06 16:35:18 β”‚ POST   β”‚ 200      β”‚ api/payments/process    β”‚ 10,567ms β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Analyze Improved Timeout Handling

php artisan chronotrace:replay timeout2 --filter=http
β”Œβ”€ HTTP EVENTS (3 requests) ───────────────────────────────────┐
β”‚ [+0ms] POST https://api.stripe.com/v1/payment_intents (10,000ms) ⚠️ TIMEOUT
β”‚        Request: Same payment data with proper idempotency
β”‚        Response: TIMEOUT_ERROR (expected with 10s timeout)
β”‚
β”‚ [+10,100ms] GET https://api.stripe.com/v1/payment_intents/pi_1234567890 (234ms) βœ…
β”‚            Request: Status check after timeout
β”‚            Response Status: 200 OK
β”‚            Response: {"status": "succeeded", "amount": 29999}
β”‚            βœ… Payment verified as successful!
β”‚
β”‚ [+10,400ms] POST https://webhook.site/success-endpoint (123ms) βœ…
β”‚            Request: Success notification
β”‚            Response Status: 200 OK
β”‚            βœ… Correct success handling despite initial timeout
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“ˆ Integration Improvements Summary

Metric Before After Improvement
Failure Rate 10% 2% 80% reduction
Average Response Time 5,500ms 1,800ms 67% faster
Timeout Recovery 0% 95% 95% success rate
User Experience Poor Good Async processing
Double Charges 15 cases 0 cases 100% eliminated

🎯 Advanced API Debugging Techniques

1. API Response Analysis

# Analyze API response patterns
php artisan chronotrace:replay trace_id --filter=http --format=json | jq '.http_events[] | {url, status, duration, response_size}'

# Find slow API endpoints
php artisan chronotrace:list --min-duration=2000 | \
  xargs -I {} php artisan chronotrace:replay {} --filter=http --min-duration=1000

2. Error Pattern Recognition

// Analyze error patterns across traces
class APIErrorAnalyzer
{
    public function analyzeErrorPatterns($timeframe = '24 hours')
    {
        $traces = ChronoTrace::getTraces()
            ->where('created_at', '>=', now()->sub($timeframe))
            ->where('status', '>=', 400);
            
        $errorPatterns = [];
        
        foreach ($traces as $trace) {
            $httpEvents = $trace->getHttpEvents();
            
            foreach ($httpEvents as $event) {
                if ($event['status'] >= 400) {
                    $pattern = [
                        'endpoint' => $this->extractEndpoint($event['url']),
                        'status' => $event['status'],
                        'error_type' => $this->categorizeError($event),
                        'frequency' => 1
                    ];
                    
                    $key = md5(json_encode($pattern));
                    
                    if (isset($errorPatterns[$key])) {
                        $errorPatterns[$key]['frequency']++;
                    } else {
                        $errorPatterns[$key] = $pattern;
                    }
                }
            }
        }
        
        return collect($errorPatterns)->sortByDesc('frequency');
    }
}

3. API Performance Monitoring

// Monitor API performance metrics
class APIPerformanceMonitor
{
    public function trackAPIMetrics($trace)
    {
        $httpEvents = $trace->getHttpEvents();
        
        foreach ($httpEvents as $event) {
            $metrics = [
                'endpoint' => $this->normalizeEndpoint($event['url']),
                'method' => $event['method'],
                'status' => $event['status'],
                'duration' => $event['duration'],
                'request_size' => $event['request_size'] ?? 0,
                'response_size' => $event['response_size'] ?? 0,
                'timestamp' => $event['timestamp'],
            ];
            
            // Store in time-series database
            InfluxDB::write('api_metrics', $metrics);
            
            // Check for performance alerts
            if ($event['duration'] > 5000) {
                $this->sendSlowAPIAlert($metrics);
            }
            
            if ($event['status'] >= 500) {
                $this->sendAPIErrorAlert($metrics);
            }
        }
    }
}

πŸ“‹ API Integration Debugging Checklist

Before Implementation

  • Define timeout strategies for each API
  • Plan retry logic and exponential backoff
  • Implement idempotency for critical operations
  • Design circuit breaker patterns
  • Plan async processing for slow APIs

During Development

  • Test timeout scenarios
  • Verify retry behavior
  • Test error response handling
  • Validate status checking mechanisms
  • Test rate limit handling

Production Monitoring

  • Monitor API response times
  • Track error rates by endpoint
  • Set up alerting for failures
  • Monitor circuit breaker states
  • Track timeout recovery success

Ongoing Optimization

  • Analyze API usage patterns
  • Optimize frequently used endpoints
  • Review and adjust timeout values
  • Update retry strategies based on data
  • Implement caching where appropriate

πŸ”§ Pro Tips for API Debugging

1. Use Request/Response Logging

// Log all API interactions
Http::macro('withLogging', function () {
    return $this->beforeSending(function ($request) {
        Log::info('API Request', [
            'url' => $request->url(),
            'method' => $request->method(),
            'headers' => $request->headers(),
            'body' => $request->body()
        ]);
    })->withMiddleware(function ($response) {
        Log::info('API Response', [
            'status' => $response->status(),
            'headers' => $response->headers(),
            'body' => $response->body(),
            'duration' => $response->handlerStats()['total_time'] ?? null
        ]);
    });
});

2. Implement Health Checks

// API health monitoring
class APIHealthChecker
{
    public function checkPaymentGateway()
    {
        try {
            $response = Http::timeout(5)->get('https://api.stripe.com/v1/account');
            return $response->successful();
        } catch (Exception $e) {
            return false;
        }
    }
}

3. Use Mock Services for Testing

// Mock external APIs during testing
class PaymentServiceTest extends TestCase
{
    public function test_payment_timeout_handling()
    {
        Http::fake([
            'api.stripe.com/*' => Http::response([], 408), // Timeout
        ]);
        
        $this->expectException(PaymentTimeoutException::class);
        
        $paymentService = new PaymentService();
        $paymentService->processPayment($this->getTestPaymentData());
    }
}

πŸ“š Related Documentation


Result: Payment system reliability improved from 90% to 98% success rate, with 67% faster response times and 100% elimination of double-charging incidents!

Clone this wiki locally