Back to Blog
BackendAug 5, 20268 min read

API Integration Best Practices for Web Applications

Practical patterns for integrating third-party APIs in Laravel and Next.js applications, covering error handling, retry logic, caching, and webhook processing.

Why API Integration Matters

Most modern applications depend on third-party APIs — payment gateways, email services, SMS providers, healthcare data APIs, and more. How you integrate these APIs directly affects your application's reliability.

From payment integrations to healthcare data APIs, I've learned that treating API integrations as first-class citizens in your codebase saves time and prevents production issues.

Create a Dedicated Service Layer

Never call third-party APIs directly from controllers. Create service classes:

class PaymentService
{
    private string $apiKey;
    private string $baseUrl;

    public function __construct()
    {
        $this->apiKey = config('services.payment.api_key');
        $this->baseUrl = config('services.payment.base_url');
    }

    public function createPayment(array $data): array
    {
        return $this->request('POST', '/payments', $data);
    }

    private function request(string $method, string $endpoint, array $data = []): array
    {
        $response = Http::withHeaders([
            'Authorization' => 'Bearer ' . $this->apiKey,
            'Content-Type' => 'application/json',
        ])->timeout(30)->$method($this->baseUrl . $endpoint, $data);

        if ($response->failed()) {
            throw new PaymentException($response->body());
        }

        return $response->json();
    }
}

Error Handling and Retries

APIs fail. Your code should handle failures gracefully:

use Illuminate\Support\Facades\Http;

class SmsService
{
    public function send(string $phone, string $message): bool
    {
        $attempts = 3;

        for ($i = 0; $i < $attempts; $i++) {
            try {
                $response = Http::timeout(10)
                    ->post($this->baseUrl . '/send', [
                        'phone' => $phone,
                        'message' => $message,
                    ]);

                if ($response->successful()) {
                    return true;
                }
            } catch (Exception $e) {
                if ($i === $attempts - 1) {
                    Log::error('SMS delivery failed', [
                        'phone' => $phone,
                        'error' => $e->getMessage(),
                    ]);
                    return false;
                }
                sleep(2 ** $i); // Exponential backoff
            }
        }

        return false;
    }
}

Caching API Responses

Cache responses when the data doesn't change frequently:

public function getExchangeRates(): array
{
    return Cache::remember('exchange_rates', 3600, function () {
        return Http::get('https://api.example.com/rates')->json();
    });
}

Webhook Processing

Webhooks need idempotency — the same event might arrive multiple times:

public function handleWebhook(Request $request)
{
    $eventId = $request->input('event_id');

    // Check if already processed
    if (WebhookEvent::where('event_id', $eventId)->exists()) {
        return response()->json(['status' => 'already_processed']);
    }

    // Process the event
    $this->processEvent($request->all());

    // Record it
    WebhookEvent::create(['event_id' => $eventId]);

    return response()->json(['status' => 'processed']);
}

Key Takeaways

  • Wrap third-party API calls in dedicated service classes
  • Always implement retry logic with exponential backoff
  • Cache API responses when appropriate
  • Make webhook handlers idempotent
  • Log API failures for debugging and monitoring