Chapter 20: Key Takeaways

Working with External APIs and Integrations

  1. Use async HTTP clients for multi-service applications. When your application calls multiple external APIs, httpx.AsyncClient with asyncio.gather() lets you execute calls concurrently, dramatically reducing total latency. A dashboard aggregating five APIs can load in 500ms instead of 2500ms.

  2. Always set explicit timeouts on external API calls. A hung external service can cascade into blocking your entire application. Set connect, read, write, and pool timeouts separately based on each service's expected behavior. A sensible default is 5 seconds for connection and 10 seconds for reading.

  3. Match the OAuth 2.0 grant type to your use case. Use Authorization Code for web apps acting on behalf of users, Client Credentials for server-to-server communication without user context, Authorization Code with PKCE for single-page and mobile apps, and Device Code for CLI tools and limited-input devices.

  4. Never handle raw payment card data. Use client-side tokenization so card numbers go directly to the payment processor. Your server only receives tokens. This reduces PCI compliance burden from SAQ D to SAQ A and eliminates an entire category of security risk.

  5. Make all payment operations idempotent. Use deterministic idempotency keys (based on customer, invoice, and amount) rather than random UUIDs. This ensures that retries -- even from different server instances after a restart -- are recognized as duplicates by the payment processor.

  6. Verify webhook signatures before processing. Webhooks are HTTP requests from external services to your application. Without signature verification, anyone who discovers your webhook URL could send fake events. Use HMAC-SHA256 with a shared secret to cryptographically verify authenticity.

  7. Implement exponential backoff with jitter for retries. Retry transient failures (5xx errors, timeouts, 429 rate limits) with increasing delays and random jitter. This prevents the thundering herd problem where many clients retry simultaneously and overwhelm a recovering service.

  8. Use circuit breakers to protect against sustained outages. When an external service is down, a circuit breaker stops sending requests after a threshold of failures. This protects both your application (from timeouts) and the external service (from additional load). The three states are Closed (normal), Open (failing, reject immediately), and Half-Open (testing recovery).

  9. Classify integrations as critical or optional and degrade gracefully. Not all external services are equally important. When a weather API fails, show cached or placeholder data. When a payment API fails, surface the error clearly. The application should always provide the best possible experience given current conditions.

  10. Cache API responses with appropriate TTLs per data type. Geocoding results can be cached for days. Weather data for 10 minutes. Currency rates for an hour. Effective caching reduces API call volume by 80% or more, controlling costs and staying within rate limits.

  11. Normalize external API responses at the boundary. Build Pydantic models that translate each external API's unique response format into your application's internal data structures. This isolates your code from API changes and makes provider swapping straightforward.

  12. Handle webhook race conditions with retry-and-store patterns. Webhooks can arrive before your application finishes processing the originating request. Implement short retries for missing records, and store unmatched events for later processing to achieve eventual consistency.

  13. Use the Strategy Pattern for multi-channel notifications. Define a common interface for notification channels (email, SMS, Slack, push) and implement each channel behind that interface. This lets you add new channels without modifying core notification logic.

  14. AI coding assistants accelerate integration boilerplate but not architecture. AI excels at generating API clients, Pydantic models, retry logic, and authentication flows. The human developer's value is in architectural decisions: what to cache, which services are critical, how to handle partial failures, and what thresholds to set.

  15. Test integrations at three levels. Unit tests with mocked HTTP responses for business logic. Integration tests against sandbox/test environments for end-to-end flows. Chaos tests that simulate service outages, slow responses, and malformed data to verify resilience patterns work correctly.