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']);
}