Adds composio-sdk/ with SKILL.md, AGENTS.md, and 18 rule files covering Tool Router, direct execution, triggers, and auth patterns. Source: composiohq/skills Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
228 lines
5.3 KiB
Markdown
228 lines
5.3 KiB
Markdown
---
|
|
title: Verify Webhooks for Production (Recommended)
|
|
impact: CRITICAL
|
|
description: Use webhook verification for reliable, scalable event delivery in production
|
|
tags: [triggers, webhooks, production, security, verification, hmac]
|
|
---
|
|
|
|
# Webhook Verification for Production
|
|
|
|
Webhooks are the **production-ready** way to receive trigger events. Provides reliable delivery, automatic retries, and works with serverless.
|
|
|
|
## Setup with listenToTriggers()
|
|
|
|
```typescript
|
|
import express from 'express';
|
|
import { Composio } from '@composio/core';
|
|
|
|
const app = express();
|
|
const composio = new Composio({ apiKey: process.env.COMPOSIO_API_KEY });
|
|
|
|
// Automatic webhook verification and handling
|
|
await composio.triggers.listenToTriggers(app, async (event) => {
|
|
console.log('Webhook:', event.triggerSlug);
|
|
console.log('User:', event.userId);
|
|
console.log('Payload:', event.payload);
|
|
|
|
await handleEvent(event);
|
|
});
|
|
|
|
app.listen(3000);
|
|
```
|
|
|
|
**What it does:**
|
|
- Creates `/composio/triggers` endpoint
|
|
- Verifies webhook signatures automatically
|
|
- Parses and validates payloads
|
|
- Calls callback with verified events
|
|
|
|
## Manual Verification
|
|
|
|
For custom endpoints:
|
|
|
|
```typescript
|
|
import { verifyWebhookSignature } from '@composio/core';
|
|
|
|
app.post('/custom/webhook', express.raw({ type: 'application/json' }), (req, res) => {
|
|
const signature = req.headers['x-composio-signature'];
|
|
const payload = req.body;
|
|
|
|
const isValid = verifyWebhookSignature(
|
|
payload,
|
|
signature,
|
|
process.env.COMPOSIO_WEBHOOK_SECRET
|
|
);
|
|
|
|
if (!isValid) {
|
|
return res.status(401).json({ error: 'Invalid signature' });
|
|
}
|
|
|
|
const event = JSON.parse(payload);
|
|
handleEvent(event);
|
|
res.json({ success: true });
|
|
});
|
|
```
|
|
|
|
## Event Structure
|
|
|
|
```typescript
|
|
interface WebhookEvent {
|
|
triggerSlug: string;
|
|
userId: string;
|
|
connectedAccountId: string;
|
|
payload: object;
|
|
metadata: {
|
|
triggerId: string;
|
|
timestamp: string;
|
|
webhookId: string;
|
|
};
|
|
}
|
|
```
|
|
|
|
## Processing Patterns
|
|
|
|
### Route by Trigger Type
|
|
|
|
```typescript
|
|
async function handleEvent(event: WebhookEvent) {
|
|
switch (event.triggerSlug) {
|
|
case 'GMAIL_NEW_GMAIL_MESSAGE':
|
|
await handleGmail(event.userId, event.payload);
|
|
break;
|
|
case 'GITHUB_COMMIT_EVENT':
|
|
await handleGithub(event.userId, event.payload);
|
|
break;
|
|
case 'SLACK_NEW_MESSAGE':
|
|
await handleSlack(event.userId, event.payload);
|
|
break;
|
|
}
|
|
}
|
|
```
|
|
|
|
### With Error Handling
|
|
|
|
```typescript
|
|
await composio.triggers.listenToTriggers(app, async (event) => {
|
|
try {
|
|
await processEvent(event);
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
// Don't throw - acknowledge webhook received
|
|
}
|
|
});
|
|
```
|
|
|
|
### With Idempotency
|
|
|
|
```typescript
|
|
await composio.triggers.listenToTriggers(app, async (event) => {
|
|
const webhookId = event.metadata.webhookId;
|
|
|
|
// Check if already processed
|
|
if (await isProcessed(webhookId)) {
|
|
console.log('Duplicate webhook, skipping');
|
|
return;
|
|
}
|
|
|
|
// Mark as processed
|
|
await markProcessed(webhookId);
|
|
|
|
// Process event
|
|
await handleEvent(event);
|
|
});
|
|
```
|
|
|
|
## Configuration
|
|
|
|
Set webhook URL in Composio dashboard:
|
|
|
|
1. Go to [platform.composio.dev](https://platform.composio.dev)
|
|
2. **Settings** > **Webhooks**
|
|
3. Set URL: `https://your-app.com/composio/triggers`
|
|
|
|
**Requirements:**
|
|
- HTTPS URL (publicly accessible)
|
|
- Respond with 200 OK within 30 seconds
|
|
- Handle concurrent requests
|
|
|
|
## Testing Locally
|
|
|
|
Use ngrok:
|
|
|
|
```bash
|
|
ngrok http 3000
|
|
# Use https://abc123.ngrok.io/composio/triggers in dashboard
|
|
```
|
|
|
|
## Security
|
|
|
|
- **Always verify signatures** - Use `listenToTriggers()` or manual verification
|
|
- **HTTPS only** - Never HTTP in production
|
|
- **Keep secrets secure** - Environment variables only
|
|
- **Validate payloads** - Check required fields
|
|
- **Handle errors gracefully** - Log, don't throw
|
|
- **Implement idempotency** - Use webhookId to deduplicate
|
|
|
|
## Common Issues
|
|
|
|
**401 Unauthorized:**
|
|
- Invalid signature - check webhook secret
|
|
- Wrong secret - verify environment variable
|
|
|
|
**Timeout:**
|
|
- Processing > 30 seconds - move to background queue
|
|
- Return 200 OK immediately
|
|
|
|
**Duplicates:**
|
|
- Webhooks may deliver multiple times
|
|
- Use webhookId for idempotency
|
|
|
|
## Complete Example
|
|
|
|
```typescript
|
|
import express from 'express';
|
|
import { Composio } from '@composio/core';
|
|
|
|
const app = express();
|
|
const composio = new Composio({ apiKey: process.env.COMPOSIO_API_KEY });
|
|
|
|
await composio.triggers.listenToTriggers(app, async (event) => {
|
|
try {
|
|
// Idempotency check
|
|
if (await isProcessed(event.metadata.webhookId)) {
|
|
return;
|
|
}
|
|
|
|
// Process
|
|
switch (event.triggerSlug) {
|
|
case 'GMAIL_NEW_GMAIL_MESSAGE':
|
|
await sendNotification(event.userId, {
|
|
title: 'New Email',
|
|
body: event.payload.subject
|
|
});
|
|
break;
|
|
}
|
|
|
|
// Mark processed
|
|
await markProcessed(event.metadata.webhookId);
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
}
|
|
});
|
|
|
|
app.get('/health', (req, res) => res.json({ status: 'ok' }));
|
|
|
|
app.listen(3000, () => {
|
|
console.log('Webhook server running on port 3000');
|
|
});
|
|
```
|
|
|
|
## Key Points
|
|
|
|
- **Production standard** - Use webhooks, not subscribe()
|
|
- **listenToTriggers()** - Handles verification automatically
|
|
- **HTTPS required** - Security requirement
|
|
- **Quick response** - Return 200 OK within 30s
|
|
- **Idempotency** - Handle duplicates with webhookId
|
|
- **Error handling** - Log but don't throw
|