613 lines
13 KiB
Markdown
613 lines
13 KiB
Markdown
# 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(
|
|
<ClerkProvider publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY}>
|
|
<App />
|
|
</ClerkProvider>
|
|
)
|
|
```
|
|
|
|
**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<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**:
|
|
```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 (
|
|
<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]
|