Files
composio-skills-reference/composio-sdk/rules/triggers-webhook.md
sohamganatra b8b711dff6 Add Composio SDK skill with rules and agent config
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>
2026-02-05 22:54:21 -08:00

5.3 KiB

title, impact, description, tags
title impact description tags
Verify Webhooks for Production (Recommended) CRITICAL Use webhook verification for reliable, scalable event delivery in production
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()

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:

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

interface WebhookEvent {
  triggerSlug: string;
  userId: string;
  connectedAccountId: string;
  payload: object;
  metadata: {
    triggerId: string;
    timestamp: string;
    webhookId: string;
  };
}

Processing Patterns

Route by Trigger Type

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

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

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
  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:

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

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