Subscribing to triggers
Webhooks
Webhooks are the recommended way to receive trigger events in production. To start receiving events, create a webhook subscription with your endpoint URL and select which event types you want to receive. You can subscribe to one or both:
| Event type | Description |
|---|---|
composio.trigger.message | Fired when a trigger receives data from an external service |
composio.connected_account.expired | Fired when a connected account expires and needs re-authentication. See Subscribing to connection expiry events. |
Set your webhook URL in the dashboard settings or via the Webhook Subscriptions API:
curl -X POST https://backend.composio.dev/api/v3/webhook_subscriptions \
-H "X-API-KEY: <your-composio-api-key>" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://example.com/webhook",
"enabled_events": ["composio.trigger.message"]
}'The response includes a secret for verifying webhook signatures. This is only returned at creation time or when you rotate the secret. Store it securely.
Handling events
All events arrive at the same endpoint. Route on the type field to handle each event type:
Inspect the payload schema for a trigger before writing your handler. See Webhook payload (V3) for the full event structure.
from composio import WebhookEventType
@app.post("/webhook")
async def webhook_handler(request: Request):
payload = await request.json()
event_type = payload.get("type")
if event_type == WebhookEventType.TRIGGER_MESSAGE:
trigger_slug = payload["metadata"]["trigger_slug"]
event_data = payload["data"]
if trigger_slug == "GITHUB_COMMIT_EVENT":
print(f"New commit by {event_data['author']}: {event_data['message']}")
# Handle connected account expired events
return {"status": "ok"}export default async function webhookHandler(req: NextApiRequest, res: NextApiResponse) {
const payload = req.body;
if (payload.type === 'composio.trigger.message') {
const triggerSlug = payload.metadata.trigger_slug;
const eventData = payload.data;
if (triggerSlug === 'GITHUB_COMMIT_EVENT') {
console.log(`New commit by ${eventData.author}: ${eventData.message}`);
}
}
// Handle connected account expired events
res.status(200).json({ status: 'ok' });
}Always verify webhook signatures in production to ensure payloads are authentic.
Inspecting trigger payload schemas
Each trigger type defines the schema of event data it sends. Use get_type()/getType() to inspect it before writing your handler:
from composio import Composio
composio = Composio()
trigger_type = composio.triggers.get_type("GITHUB_COMMIT_EVENT")
print(trigger_type.payload)
# Returns: {"properties": {"author": {...}, "id": {...}, "message": {...}, "timestamp": {...}, "url": {...}}}import { Composio } from '@composio/core';
const composio = new Composio();
const triggerType = await composio.triggers.getType("GITHUB_COMMIT_EVENT");
console.log(triggerType.payload);
// Returns: {"properties": {"author": {...}, "id": {...}, "message": {...}, "timestamp": {...}, "url": {...}}}The payload schema tells you what fields will be in the data object of the webhook event.
Webhook payload (V3)
New organizations receive V3 payloads by default. V3 separates event metadata from the actual event data:
{
"id": "msg_abc123",
"type": "composio.trigger.message",
"metadata": {
"log_id": "log_abc123",
"trigger_slug": "GITHUB_COMMIT_EVENT",
"trigger_id": "ti_xyz789",
"connected_account_id": "ca_def456",
"auth_config_id": "ac_xyz789",
"user_id": "user-id-123435"
},
"data": {
"commit_sha": "a1b2c3d",
"message": "fix: resolve null pointer",
"author": "jane"
},
"timestamp": "2026-01-15T10:30:00Z"
}See webhook payload versions for V2 and V1 formats.
Testing locally
SDK subscriptions
Subscribe to trigger events directly through the SDK without setting up a webhook endpoint. Uses WebSockets under the hood.
from composio import Composio
composio = Composio()
subscription = composio.triggers.subscribe()
@subscription.handle(trigger_id="your_trigger_id")
def handle_event(data):
print(f"Event received: {data}")
subscription.wait_forever()import { Composio } from '@composio/core';
const composio = new Composio();
await composio.triggers.subscribe(
(data) => {
console.log('Event received:', data);
},
{ triggerId: 'your_trigger_id' }
);Using ngrok
To test the full webhook flow locally, use ngrok to expose your local server:
ngrok http 8000Then use the ngrok URL as your webhook endpoint:
curl -X POST https://backend.composio.dev/api/v3/webhook_subscriptions \
-H "X-API-KEY: <your-composio-api-key>" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://your-ngrok-url.ngrok-free.app/webhook",
"enabled_events": ["composio.trigger.message"]
}'Events will now be forwarded to your local server at http://localhost:8000/webhook.
Identifying trigger events
Every webhook event includes a metadata object that tells you exactly where it came from:
| Field | What it tells you |
|---|---|
metadata.trigger_id | Which trigger instance fired this event |
metadata.trigger_slug | The type of trigger (e.g., GITHUB_COMMIT_EVENT) |
metadata.connected_account_id | Which connected account it belongs to |
metadata.user_id | Which user it's for |
metadata.auth_config_id | Which auth config was used |
Use trigger_id to match events to a specific trigger instance, or trigger_slug to handle all events of a certain type. These fields can also be passed as filters when using SDK subscriptions.