api-integration

Integrates with third-party APIs and services - client design, authentication flows, rate limit handling, webhook processing, retry logic, and vendor abstraction. Use when consuming external APIs, handling webhooks, or wrapping third-party SDKs.

API Integration

You don't control external APIs. They change, they break, they go down, and they rate-limit you. Design for all of this.

Client Design

The Wrapper Pattern

Never call a third-party API directly from business logic. Always wrap it.

Business Logic → Your Wrapper → Third-Party SDK/HTTP Client → External API

Why:

  • Swap vendors without changing business logic
  • Add retry logic, circuit breaker, logging in one place
  • Mock the wrapper in tests (not the HTTP client)
  • Normalize error types to your domain

Wrapper Structure

services/
  payments/
    client.ts          # HTTP client, auth, retry logic
    types.ts           # Your domain types (not vendor types)
    mapper.ts          # Vendor response → your types
    errors.ts          # Vendor errors → your error types

Rules:

  • Vendor types NEVER leak into business logic
  • Map vendor responses to your own types immediately
  • Map vendor errors to your own error types
  • Business logic depends on YOUR interface, not the vendor's

Authentication Patterns

API Key

Header: Authorization: Bearer sk_live_xxx
   or: X-API-Key: xxx
  • Store in secret manager, never in code
  • Rotate on schedule
  • Use separate keys per environment

OAuth2 Client Credentials

1. Exchange client_id + client_secret for access token
2. Cache the access token until expiry
3. Refresh BEFORE it expires (with buffer)
4. Retry once with fresh token on 401

Token refresh pattern:

Request fails with 401
  → Is this a retry already? YES → fail (don't loop)
  → Refresh the token
  → Retry the original request with new token

Webhook Signature Verification

1. Extract signature from header (e.g., X-Signature-256)
2. Compute HMAC-SHA256 of raw request body with your webhook secret
3. Compare (constant-time!) your computed signature with the received one
4. Reject if mismatch - someone is spoofing webhooks

Never skip signature verification. It's the only thing preventing attackers from injecting fake events.

Rate Limit Handling

Reactive (Handle 429)

Request → 429 Too Many Requests
  → Read Retry-After header
  → Wait that long
  → Retry

Proactive (Stay Under Limits)

- Track your request count per time window
- Implement client-side rate limiting (token bucket, leaky bucket)
- Queue requests and drain at the allowed rate
- Log when approaching limits (80% threshold)

Rate Limit Response Headers

X-RateLimit-Limit: 100        # Max requests per window
X-RateLimit-Remaining: 23     # Remaining in current window
X-RateLimit-Reset: 1710500000 # Unix timestamp when window resets
Retry-After: 30               # Seconds to wait (on 429)

Retry Strategy

Retryable:     429, 500, 502, 503, 504, connection timeout, DNS failure
NOT retryable: 400, 401, 403, 404, 409, 422 (client errors - fix your request)
attempt 1: immediate
attempt 2: 200ms + jitter
attempt 3: 800ms + jitter
attempt 4: 3200ms + jitter
give up → log, DLQ, or return error

Rules:

  • Only retry idempotent operations (or use idempotency keys)
  • Cap total retry time (don't retry for 10 minutes)
  • Log every retry with attempt number and error
  • Circuit break if too many retries are happening

Idempotency Keys

For non-idempotent operations (creating charges, sending emails), use idempotency keys:

POST /v1/charges
Idempotency-Key: charge_req_abc123
{ amount: 5000, currency: "usd" }

If the request fails ambiguously (timeout, network error), retry with the same idempotency key. The API returns the original result instead of creating a duplicate.

Generate idempotency keys from your domain: charge_{order_id}_{attempt} - deterministic and meaningful.

Webhook Processing

Receiving Webhooks

1. Verify signature (ALWAYS)
2. Return 200 immediately (don't process inline)
3. Queue the event for async processing
4. Process from queue with retry logic
5. Idempotent handling (same event may arrive twice)

Why return 200 immediately? If your processing takes > 5s, the sender may time out and retry, causing duplicates.

Webhook Event Handling

Receive webhook event
  → Verify signature
  → Store raw event in database
  → Return 200
  → Async: process event
    → Check: have I already processed this event ID?
      YES → skip (idempotent)
      NO  → process and record event ID as processed

Webhook Reliability

External webhooks WILL fail. Design for it:

  • Don't depend on webhooks for critical flows (poll as backup)
  • Store raw webhook payload for replay
  • Monitor: are expected webhooks arriving? Alert on silence.

Timeout Configuration

Every external call needs a timeout. No exceptions.

Timeout TypeTypical ValuePurpose
Connection timeout5sGive up connecting
Read timeout30sGive up waiting for response
Total timeout60sGive up on the entire operation

Without timeouts: One slow API call blocks your thread/connection forever, eventually exhausting your pool and taking down your service.

Testing

Unit Tests

Mock your wrapper (not the HTTP client). Test that your code handles:

  • Successful responses (mapped to your types correctly)
  • Error responses (mapped to your error types)
  • Timeouts
  • Rate limiting (429 handling)
  • Auth failures (401 → refresh → retry)

Contract Tests

Verify your expectations match the API's actual behavior:

  • Record real API responses
  • Replay them in tests
  • Detect when the API changes (before production breaks)

Sandbox/Test Mode

Most APIs have sandbox environments. Use them in staging:

  • Stripe test mode, Twilio test credentials, etc.
  • Never call production APIs from tests

Reference: reference/integration-patterns.md - rate limiting strategies, auth pattern comparison, webhook reliability, circuit breaker, SDK vs HTTP decision, vendor API patterns, timeout strategy.

Anti-Patterns

Anti-PatternProblemFix
Direct API calls in business logicTight coupling, untestable, no retryWrap in a service with its own interface
No timeoutThread blocked forever on slow APISet connection + read + total timeouts
Retry everythingRetrying 400 errors wastes quotaOnly retry transient errors (5xx, timeouts)
Vendor types in domainAPI change breaks everythingMap to your own types at the boundary
Trust webhook without verifyingAttackers can inject fake eventsAlways verify signatures
Process webhook synchronouslyTimeouts cause duplicate deliveriesReturn 200 fast, process async