# How to Manage Webhook Triggers and Event Subscriptions for Real-Time Responses in Composio

> Manage webhook triggers and event subscriptions securely with Composio. Verify signatures and stream real-time events with optional filtering for instant responses.

- Repository: [Composio/composio](https://github.com/composiohq/composio)
- Tags: how-to-guide
- Published: 2026-02-19

---

**Use `composio.triggers.verifyWebhook()` to validate HMAC-SHA256 signatures on incoming HTTP webhooks and `composio.triggers.subscribe()` to establish Pusher-based connections for streaming real-time trigger events with optional filtering.**

Composio delivers real-time integration events through two primary channels: HTTP webhooks for push-based notifications and a Pusher-based subscription API for persistent event streaming. This guide demonstrates how to implement secure webhook verification and manage live event subscriptions using the Composio TypeScript SDK, referencing the actual implementation in the `ComposioHQ/composio` repository.

## Verifying Inbound Webhook Signatures

All webhook requests from Composio include cryptographic signatures that must be validated before processing to ensure payload integrity and authenticity. The verification logic resides in [`ts/packages/core/src/models/Triggers.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/core/src/models/Triggers.ts) within the `verifyWebhook` method (lines 555-704).

### The Verification Workflow

The `Triggers.verifyWebhook` function executes a four-step validation process:

1. **Parameter validation** against `VerifyWebhookParamsSchema`
2. **Timestamp tolerance** checking (defaulting to 5 minutes, configurable via the `tolerance` parameter)
3. **HMAC-SHA256 signature verification** using the `hmacSha256Base64` helper from [`ts/packages/core/src/utils/crypto.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/core/src/utils/crypto.ts)
4. **Payload parsing** across version schemas

The signature format follows `v1,base64Signature`, computed by signing the string `<webhook-id>.<timestamp>.<payload>` with your webhook secret. The implementation uses `timingSafeEqual` to prevent timing attacks during the comparison operation.

### Handling Multi-Version Payloads

Composio supports three webhook payload versions (V1-V3). The system attempts sequential parsing (V3 → V2 → V1) using `tryParseVersionedPayload`. If all schemas fail, the method throws `ComposioWebhookPayloadError`. Upon successful parsing, version-specific normalizers—`normalizeV1Payload`, `normalizeV2Payload`, and `normalizeV3Payload` (lines 558-736)—convert the data into a stable `IncomingTriggerPayload` structure regardless of the original version.

```typescript
import { Composio, ComposioWebhookSignatureVerificationError } from '@composio/core';

// Inside your Express/Bun route handler
const result = await composio.triggers.verifyWebhook({
  payload: rawBody.toString(),
  signature: req.headers['webhook-signature'] as string,
  id: req.headers['webhook-id'] as string,
  timestamp: req.headers['webhook-timestamp'] as string,
  secret: process.env.COMPOSIO_WEBHOOK_SECRET!,
});

// result.payload is now a normalized IncomingTriggerPayload
console.log(result.payload.triggerSlug, result.payload.toolkitSlug);

```

## Subscribing to Live Trigger Events

For real-time event streaming without managing HTTP endpoints, use the Pusher-based subscription system implemented in `Triggers.subscribe` (lines 434-465).

### Establishing Pusher Connections

The subscription method creates a `PusherService` instance (defined in [`ts/packages/core/src/services/pusher/Pusher.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/core/src/services/pusher/Pusher.ts)) connecting to Composio's Pusher cluster. It validates optional filters through `TriggerSubscribeParamSchema`, then listens for raw messages that undergo the same version detection logic as webhooks via `parsePusherPayload`. Errors in your callback are logged but do not terminate the subscription loop.

### Filtering Events by Toolkit and Type

Apply granular filters through the `TriggerSubscribeParams` interface to reduce processing overhead:

- **`toolkits`**: Array of toolkit identifiers (e.g., `['github', 'slack']`)
- **`triggerSlug`**: Specific event types (e.g., `['GITHUB_PUSH_EVENT']`)
- **`connectedAccountId`**: Events scoped to a particular integration account
- **`userId`**: User-specific event filtering
- **`triggerData`**: Exact matching on metadata objects

The filtering logic executes in `Triggers.shouldSendTriggerAfterFilters` (lines 471-516).

```typescript
await composio.triggers.subscribe(
  (payload) => {
    // Executed immediately when trigger fires
    console.log(`${payload.triggerSlug} from ${payload.toolkitSlug}`);
    // Dispatch to internal queues or processing logic
  },
  {
    toolkits: ['github'],
    triggerSlug: ['GITHUB_PUSH_EVENT', 'GITHUB_PULL_REQUEST_EVENT'],
    connectedAccountId: 'acc_12345'
  }
);

```

## Managing Connection Lifecycle

Terminate subscriptions cleanly to prevent resource leaks, particularly during application shutdown or reconnection scenarios.

```typescript
// Close Pusher socket and cleanup listeners
await composio.triggers.unsubscribe();

```

This method is implemented in `Triggers.unsubscribe` (lines 600-607) and ensures proper disposal of the underlying Pusher connection.

## Security Best Practices

When managing webhook triggers and event subscriptions, implement these safeguards derived from [`ts/docs/advanced/webhook-verification.md`](https://github.com/ComposioHQ/composio/blob/main/ts/docs/advanced/webhook-verification.md) (lines 38-46) and the crypto utilities in [`ts/packages/core/src/utils/crypto.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/core/src/utils/crypto.ts):

- **Never expose webhook secrets** in client-side code or version control; store `COMPOSIO_WEBHOOK_SECRET` in server-only environment variables
- **Always verify signatures** using `verifyWebhook` before processing payload data
- **Enforce timestamp tolerance** (default 5 minutes) to mitigate replay attacks
- **Use HTTPS endpoints** for webhook receivers to prevent man-in-the-middle interception
- **Implement proper error handling** for `ComposioWebhookSignatureVerificationError` (HTTP 401) and `ComposioWebhookPayloadError` (HTTP 400)

## Complete Implementation Example

Reference the full working implementation in [`ts/examples/triggers/src/webhook-server.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/examples/triggers/src/webhook-server.ts) for a minimal Bun/Express server:

```typescript
import express from 'express';
import { Composio, ComposioWebhookSignatureVerificationError } from '@composio/core';

const app = express();
const composio = new Composio({ apiKey: process.env.COMPOSIO_API_KEY });

app.post('/webhook', 
  express.raw({ type: 'application/json' }), 
  async (req, res) => {
    try {
      const result = await composio.triggers.verifyWebhook({
        payload: req.body.toString(),
        signature: req.headers['webhook-signature'] as string,
        id: req.headers['webhook-id'] as string,
        timestamp: req.headers['webhook-timestamp'] as string,
        secret: process.env.COMPOSIO_WEBHOOK_SECRET!,
      });

      console.log('✅ Verified', result.version, result.payload.triggerSlug);
      res.status(200).send('OK');
    } catch (e) {
      if (e instanceof ComposioWebhookSignatureVerificationError) {
        res.status(401).json({ error: 'Unauthorized' });
      } else {
        res.status(400).json({ error: 'Bad Request' });
      }
    }
  }
);

app.listen(3000);

```

## Summary

- **Verify signatures** using `composio.triggers.verifyWebhook()` with your secret to authenticate HMAC-SHA256 signatures and normalize payloads to `IncomingTriggerPayload`
- **Handle versioning** automatically through the SDK's sequential parsing of V1-V3 schemas in [`ts/packages/core/src/models/Triggers.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/core/src/models/Triggers.ts)
- **Subscribe to real-time events** via `composio.triggers.subscribe()` using Pusher connections, applying filters for `toolkits`, `triggerSlug`, or `connectedAccountId` to reduce noise
- **Cleanup resources** by calling `composio.triggers.unsubscribe()` during application shutdown to close sockets properly
- **Consult reference files**: [`ts/packages/core/src/models/Triggers.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/core/src/models/Triggers.ts), [`ts/packages/core/src/utils/crypto.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/core/src/utils/crypto.ts), and [`ts/packages/core/src/types/triggers.types.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/core/src/types/triggers.types.ts) for implementation details

## Frequently Asked Questions

### How do I verify webhook signatures in Composio?

Use the `composio.triggers.verifyWebhook()` method from [`ts/packages/core/src/models/Triggers.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/core/src/models/Triggers.ts) (lines 555-704). Pass the raw request body, signature header, webhook ID, timestamp, and your secret. The method validates the HMAC-SHA256 signature using the `hmacSha256Base64` utility and performs timing-safe comparisons via `timingSafeEqual` to prevent timing attacks.

### What is the difference between webhooks and Pusher subscriptions?

Webhooks deliver events via HTTP POST requests to your endpoint with signed payloads requiring cryptographic verification, ideal for serverless architectures. Pusher subscriptions maintain a persistent WebSocket connection through `composio.triggers.subscribe()`, providing immediate event streaming better suited for long-running applications requiring sub-second latency without HTTP overhead.

### How do I filter specific trigger events in real-time?

Pass a filter object to `composio.triggers.subscribe()` specifying `toolkits`, `triggerSlug`, `connectedAccountId`, `userId`, or `triggerData` criteria. The SDK evaluates these filters in `Triggers.shouldSendTriggerAfterFilters` (lines 471-516) before invoking your callback, ensuring you process only relevant events.

### How does Composio handle different webhook payload versions?

The SDK automatically normalizes V1, V2, and V3 payloads to the standard `IncomingTriggerPayload` interface through dedicated normalizer methods in [`ts/packages/core/src/models/Triggers.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/core/src/models/Triggers.ts). The `verifyWebhook` function attempts parsing each version sequentially, providing backward compatibility without requiring manual version detection in your application code.