13 KiB
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:
npm install @clerk/clerk-react @clerk/backend
Environment Variables:
# Frontend
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
# Backend (Wrangler secret)
CLERK_SECRET_KEY=sk_test_...
Frontend Integration:
// src/main.tsx
import { ClerkProvider } from '@clerk/clerk-react'
ReactDOM.createRoot(document.getElementById('root')!).render(
<ClerkProvider publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY}>
<App />
</ClerkProvider>
)
Backend Integration (JWT verification):
// 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):
{
"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:
npm install [package-name]
Environment Variables:
[SERVICE]_API_KEY=...
[SERVICE]_WEBHOOK_SECRET=...
API Client:
// 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:
// 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:
npm install openai
Environment Variables:
OPENAI_API_KEY=sk-...
API Client:
// src/lib/openai-client.ts
import OpenAI from 'openai'
export function createOpenAIClient(apiKey: string) {
return new OpenAI({ apiKey })
}
Usage:
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:
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 upuser.updated- User profile changeduser.deleted- User account deleted
Payload Example:
{
"type": "user.created",
"data": {
"id": "user_abc123",
"email_addresses": [
{ "email_address": "user@example.com" }
],
"first_name": "John",
"last_name": "Doe"
}
}
Signature Verification:
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:
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):
- Go to Webhooks section
- Add endpoint:
https://[your-app].workers.dev/api/webhooks/clerk - Select events: user.created, user.updated, user.deleted
- 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)
# 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)
# 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:
async function callWithRetry<T>(
fn: () => Promise<T>,
maxRetries = 3
): Promise<T> {
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:
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:
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:
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:
// 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:
// 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:
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:
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
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
if (!stripe.isHealthy()) {
return (
<Alert variant="warning">
Payment processing is temporarily unavailable. Please try again later.
</Alert>
)
}
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]