# Third-Party Integrations: [Project Name] **Primary Integrations**: [List main services - e.g., Clerk, Stripe, OpenAI] **Webhooks**: [Number of webhook handlers] **Last Updated**: [Date] --- ## Overview This document describes all third-party service integrations including API setup, authentication, webhooks, and error handling. **Integration Principles**: - **Environment-based config** - API keys in env vars, never committed - **Graceful degradation** - App works (reduced functionality) if service is down - **Webhook verification** - Always verify webhook signatures - **Rate limit awareness** - Respect provider rate limits - **Error handling** - Catch and log integration failures --- ## Integrations ### Clerk (Authentication) **Purpose**: User authentication and management **Docs**: https://clerk.com/docs **Dashboard**: https://dashboard.clerk.com **Setup**: ```bash npm install @clerk/clerk-react @clerk/backend ``` **Environment Variables**: ```env # Frontend VITE_CLERK_PUBLISHABLE_KEY=pk_test_... # Backend (Wrangler secret) CLERK_SECRET_KEY=sk_test_... ``` **Frontend Integration**: ```tsx // src/main.tsx import { ClerkProvider } from '@clerk/clerk-react' ReactDOM.createRoot(document.getElementById('root')!).render( ) ``` **Backend Integration** (JWT verification): ```typescript // src/middleware/auth.ts import { verifyToken } from '@clerk/backend' export async function authMiddleware(c: Context, next: Next) { const token = c.req.header('Authorization')?.replace('Bearer ', '') if (!token) { return c.json({ error: 'Unauthorized' }, 401) } try { const verified = await verifyToken(token, { secretKey: c.env.CLERK_SECRET_KEY }) c.set('userId', verified.sub) c.set('email', verified.email) await next() } catch (error) { return c.json({ error: 'Invalid token' }, 401) } } ``` **Custom JWT Template** (configured in Clerk dashboard): ```json { "email": "{{user.primary_email_address}}", "userId": "{{user.id}}", "firstName": "{{user.first_name}}", "lastName": "{{user.last_name}}" } ``` **Webhooks**: See webhook section below **Rate Limits**: 100 requests/second (Pro plan) **Error Handling**: - Token expired → Return 401, frontend refreshes token - Service down → Return 503, show maintenance message --- ### [Integration Name - e.g., Stripe] **Purpose**: [What this service provides - e.g., Payment processing] **Docs**: [Documentation URL] **Dashboard**: [Dashboard URL] **Setup**: ```bash npm install [package-name] ``` **Environment Variables**: ```env [SERVICE]_API_KEY=... [SERVICE]_WEBHOOK_SECRET=... ``` **API Client**: ```typescript // src/lib/[service]-client.ts import [ServiceSDK] from '[package-name]' export function create[Service]Client(apiKey: string) { return new [ServiceSDK]({ apiKey, // other config }) } ``` **Usage Example**: ```typescript // In route handler const client = create[Service]Client(c.env.[SERVICE]_API_KEY) const result = await client.[method]({ params }) ``` **Webhooks**: [Yes/No - see webhook section if yes] **Rate Limits**: [Limits for this service] **Error Handling**: [How to handle failures] --- ### OpenAI (AI Features) - Example **Purpose**: AI-powered features (chat, completions, embeddings) **Docs**: https://platform.openai.com/docs **Dashboard**: https://platform.openai.com **Setup**: ```bash npm install openai ``` **Environment Variables**: ```env OPENAI_API_KEY=sk-... ``` **API Client**: ```typescript // src/lib/openai-client.ts import OpenAI from 'openai' export function createOpenAIClient(apiKey: string) { return new OpenAI({ apiKey }) } ``` **Usage**: ```typescript const openai = createOpenAIClient(c.env.OPENAI_API_KEY) const response = await openai.chat.completions.create({ model: 'gpt-5', messages: [{ role: 'user', content: 'Hello!' }], stream: true }) // Stream response to client return new Response(response.body, { headers: { 'Content-Type': 'text/event-stream' } }) ``` **Rate Limits**: - Free tier: 3 requests/minute - Paid: 10,000 requests/minute **Error Handling**: ```typescript try { const response = await openai.chat.completions.create({ ... }) return response } catch (error) { if (error.code === 'rate_limit_exceeded') { return c.json({ error: 'Too many requests. Please try again later.' }, 429) } else { console.error('OpenAI error:', error) return c.json({ error: 'AI service unavailable' }, 503) } } ``` --- ## Webhooks Webhooks are HTTP callbacks from third-party services to notify our app of events. ### Webhook: Clerk User Events **Purpose**: Sync user data when Clerk users are created, updated, or deleted **Endpoint**: `POST /api/webhooks/clerk` **Events**: - `user.created` - New user signed up - `user.updated` - User profile changed - `user.deleted` - User account deleted **Payload Example**: ```json { "type": "user.created", "data": { "id": "user_abc123", "email_addresses": [ { "email_address": "user@example.com" } ], "first_name": "John", "last_name": "Doe" } } ``` **Signature Verification**: ```typescript import { Webhook } from 'svix' export async function verifyClerkWebhook( payload: string, headers: Headers, secret: string ) { const wh = new Webhook(secret) try { return wh.verify(payload, { 'svix-id': headers.get('svix-id')!, 'svix-timestamp': headers.get('svix-timestamp')!, 'svix-signature': headers.get('svix-signature')! }) } catch (error) { throw new Error('Invalid webhook signature') } } ``` **Handler**: ```typescript app.post('/api/webhooks/clerk', async (c) => { const payload = await c.req.text() const verified = await verifyClerkWebhook( payload, c.req.raw.headers, c.env.CLERK_WEBHOOK_SECRET ) const event = JSON.parse(payload) switch (event.type) { case 'user.created': await createUserInDatabase(event.data) break case 'user.updated': await updateUserInDatabase(event.data) break case 'user.deleted': await deleteUserInDatabase(event.data.id) break } return c.json({ received: true }) }) ``` **Configuration** (Clerk dashboard): 1. Go to Webhooks section 2. Add endpoint: `https://[your-app].workers.dev/api/webhooks/clerk` 3. Select events: user.created, user.updated, user.deleted 4. Copy signing secret to environment variables --- ### Webhook: [Service Name] **Purpose**: [What events this webhook handles] **Endpoint**: `POST /api/webhooks/[service]` **Events**: [List of event types] **Signature Verification**: [How to verify] **Handler**: [Implementation details] --- ## Environment Variables ### Development (.dev.vars) ```env # Clerk CLERK_SECRET_KEY=sk_test_... CLERK_WEBHOOK_SECRET=whsec_... # OpenAI OPENAI_API_KEY=sk-... # Stripe STRIPE_SECRET_KEY=sk_test_... STRIPE_WEBHOOK_SECRET=whsec_... # [Other services] ``` ### Production (Wrangler secrets) ```bash # Set secrets via CLI npx wrangler secret put CLERK_SECRET_KEY npx wrangler secret put OPENAI_API_KEY npx wrangler secret put STRIPE_SECRET_KEY # Or via dashboard # Cloudflare Dashboard → Workers → [Your Worker] → Settings → Variables ``` **Never commit secrets to git**. Use `.dev.vars` locally (gitignored) and Wrangler secrets in production. --- ## API Rate Limiting ### Handling Rate Limits **Strategy**: Exponential backoff + retry **Implementation**: ```typescript async function callWithRetry( fn: () => Promise, maxRetries = 3 ): Promise { let attempt = 0 while (attempt < maxRetries) { try { return await fn() } catch (error) { if (error.code === 'rate_limit' && attempt < maxRetries - 1) { const delay = Math.pow(2, attempt) * 1000 // 1s, 2s, 4s await new Promise(resolve => setTimeout(resolve, delay)) attempt++ } else { throw error } } } throw new Error('Max retries exceeded') } // Usage const result = await callWithRetry(() => openai.chat.completions.create({ ... }) ) ``` --- ## Error Handling ### Integration Failure Patterns **1. Service Temporarily Down**: ```typescript try { const result = await callExternalService() return result } catch (error) { console.error('Service error:', error) return c.json({ error: 'Service temporarily unavailable. Please try again later.', code: 'SERVICE_UNAVAILABLE' }, 503) } ``` **2. Invalid Credentials**: ```typescript if (error.code === 'invalid_api_key') { console.error('Invalid API key for [service]') return c.json({ error: 'Configuration error. Please contact support.', code: 'CONFIGURATION_ERROR' }, 500) } ``` **3. Rate Limited**: ```typescript if (error.code === 'rate_limit_exceeded') { return c.json({ error: 'Too many requests. Please wait a moment and try again.', code: 'RATE_LIMIT_EXCEEDED', retryAfter: error.retryAfter }, 429) } ``` --- ## Testing Integrations ### Mocking External Services **Use MSW (Mock Service Worker)** for tests: ```typescript // tests/mocks/handlers.ts import { http, HttpResponse } from 'msw' export const handlers = [ http.post('https://api.openai.com/v1/chat/completions', () => { return HttpResponse.json({ choices: [ { message: { content: 'Mocked response' } } ] }) }) ] ``` **Setup**: ```typescript // tests/setup.ts import { setupServer } from 'msw/node' import { handlers } from './mocks/handlers' const server = setupServer(...handlers) beforeAll(() => server.listen()) afterEach(() => server.resetHandlers()) afterAll(() => server.close()) ``` --- ### Testing Webhooks **Test webhook signature verification**: ```typescript describe('POST /api/webhooks/clerk', () => { it('rejects invalid signature', async () => { const res = await app.request('/api/webhooks/clerk', { method: 'POST', headers: { 'svix-signature': 'invalid_signature' }, body: JSON.stringify({ type: 'user.created', data: {} }) }) expect(res.status).toBe(401) }) it('processes valid webhook', async () => { // Generate valid signature for testing const payload = JSON.stringify({ type: 'user.created', data: {...} }) const signature = generateTestSignature(payload) const res = await app.request('/api/webhooks/clerk', { method: 'POST', headers: { 'svix-signature': signature, 'svix-id': 'msg_123', 'svix-timestamp': Date.now().toString() }, body: payload }) expect(res.status).toBe(200) }) }) ``` --- ## Monitoring ### Track Integration Health **Metrics**: - API call success rate (per service) - API call latency (average, p95, p99) - Rate limit hits - Webhook delivery success rate **Logging**: ```typescript console.log('[Integration]', { service: 'openai', method: 'chat.completions.create', success: true, latency: responseTime, tokensUsed: response.usage.total_tokens }) ``` **Alerts**: - Integration success rate < 95% → Alert - Average latency > 5s → Alert - Webhook failures > 10 in 1 hour → Alert --- ## Graceful Degradation **If integration fails, app should still function** (with reduced features). **Example**: AI chat unavailable ```typescript try { const response = await openai.chat.completions.create({ ... }) return response } catch (error) { console.error('OpenAI unavailable:', error) // Fallback: Return canned response return { content: "I'm currently unavailable. Please try again later or contact support.", fallback: true } } ``` **Example**: Payment processing down ```typescript if (!stripe.isHealthy()) { return ( Payment processing is temporarily unavailable. Please try again later. ) } ``` --- ## Security Best Practices ### API Keys - ✅ Store in environment variables - ✅ Use Wrangler secrets in production - ✅ Rotate keys periodically - ❌ Never commit to git - ❌ Never log in production ### Webhook Security - ✅ Always verify signatures - ✅ Use HTTPS endpoints only - ✅ Validate payload structure - ✅ Implement replay protection (check timestamp) - ❌ Never trust webhook data without verification ### CORS - ✅ Restrict origins to your domain - ✅ Allow only necessary methods - ❌ Don't use wildcard (*) in production --- ## Future Integrations Planned integrations: - [ ] [Service name] - [Purpose] - [ ] [Service name] - [Purpose] --- ## Integration Checklist When adding a new integration: - [ ] Install SDK/package - [ ] Add environment variables - [ ] Create API client wrapper - [ ] Implement error handling - [ ] Add rate limit handling - [ ] Setup webhook handler (if applicable) - [ ] Verify webhook signatures - [ ] Write tests (mock external calls) - [ ] Document in this file - [ ] Add monitoring/logging - [ ] Test in staging before production --- ## Revision History **v1.0** ([Date]): Initial integration documentation **v1.1** ([Date]): [Changes made]