Creating triggers
Create a trigger to start receiving events. A trigger watches for a specific event (e.g., GITHUB_COMMIT_EVENT) on a specific user's connected account. For an overview of how triggers work, see Triggers.
Prerequisites
- An auth config for the toolkit you want to monitor
- A connected account for the user whose events you want to capture
- A webhook subscription on the project, so events have somewhere to land
You can create triggers using the SDK or the Composio dashboard. With Composio-managed OAuth, Composio handles all webhook endpoint setup for you. If you bring your own OAuth app and the trigger type's requires_webhook_endpoint flag is true, the create call returns a 400 until you set up the endpoint — see Configuring the webhook endpoint below.
Using the SDK
Before creating a trigger, inspect the trigger type. The response tells you what config it requires and whether you need to set up a webhook endpoint first.
When you pass a user_id, the SDK automatically finds the user's connected account for the relevant toolkit. If the user has multiple connected accounts for the same toolkit, it uses the most recently created one. You can also pass a connected_account_id/connectedAccountId directly if you need more control.
from composio import Composio
composio = Composio()
user_id = "user-id-123435"
# Inspect the trigger type
trigger_type = composio.triggers.get_type("GITHUB_COMMIT_EVENT")
print(trigger_type.config)
# {"properties": {"owner": {...}, "repo": {...}}, "required": ["owner", "repo"]}
# Create trigger with the required config
trigger = composio.triggers.create(
slug="GITHUB_COMMIT_EVENT",
user_id=user_id,
trigger_config={"owner": "your-repo-owner", "repo": "your-repo-name"},
)
print(f"Trigger created: {trigger.trigger_id}")import { Composio } from '@composio/core';
const composio = new Composio();
const userId = 'user-id-123435';
// Inspect the trigger type
const triggerType = await composio.triggers.getType("GITHUB_COMMIT_EVENT");
console.log(triggerType.config);
// {"properties": {"owner": {...}, "repo": {...}}, "required": ["owner", "repo"]}
// Create trigger with the required config
const trigger = await composio.triggers.create(
userId,
'GITHUB_COMMIT_EVENT',
{
triggerConfig: {
owner: 'your-repo-owner',
repo: 'your-repo-name'
}
}
);
console.log(`Trigger created: ${trigger.triggerId}`);Trigger instances default to the 'latest' toolkit version. If your code parses trigger payloads programmatically against a fixed schema, you can pin a specific version at SDK initialization. See Toolkit Versioning for details.
Using the dashboard
- Navigate to Auth Configs and select the auth config for the relevant toolkit
- Navigate to Active Triggers and click Create Trigger
- Select the connected account for which you want to create a trigger
- Choose a trigger type and fill in the required configuration. If the trigger requires a webhook endpoint, the dashboard prompts you to configure one before saving.
- Click Create Trigger
Configuring the webhook endpoint
Skip this section unless all three are true: you bring your own OAuth app, the trigger type's requires_webhook_endpoint flag is true, and no matching endpoint exists yet (the create call returned a 400). With Composio-managed OAuth, Composio configures the webhook endpoint for you transparently — none of these steps apply.
A webhook_endpoint is a project-scoped resource that owns the ingress URL the provider posts to and the signing secret used to verify each request. Endpoints are keyed by (toolkit_slug, project_id, client_id), so each OAuth app gets its own URL:
https://backend.composio.dev/api/v3.1/webhook_ingress/{toolkit_slug}/{we_xxx}/trigger_eventA webhook endpoint ties one OAuth app to exactly one project. If you currently share an OAuth app across multiple Composio projects, either consolidate to one project or register separate OAuth apps per project before creating an endpoint — the provider dashboard accepts only one callback URL per OAuth app.
The walkthrough below uses Slack as the example. Substitute the toolkit slug for any other toolkit on the same flow — the API surface is identical.
Step 1: Discover what credentials the endpoint needs
Call the schema endpoint for the toolkit. The setup_fields in the response tell you exactly what to collect from the provider's app dashboard.
curl "https://backend.composio.dev/api/v3.1/webhook_endpoints/schema?toolkit_slug=slack" \
-H "x-api-key: <YOUR_COMPOSIO_API_KEY>"Sample response:
{
"toolkit_slug": "slack",
"setup_fields": {
"webhook_signing_secret": {
"display_name": "Signing Secret",
"description": "Webhook request signing secret from your Slack app dashboard",
"is_required": true,
"is_secret": true
},
"app_token": {
"display_name": "App-Level Token",
"description": "Slack xapp- token with authorizations:read scope for event authorization",
"is_required": true,
"is_secret": true
}
}
}Step 2: Create the endpoint
curl -X POST "https://backend.composio.dev/api/v3.1/webhook_endpoints" \
-H "x-api-key: <YOUR_COMPOSIO_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"toolkit_slug": "slack",
"client_id": "<YOUR_OAUTH_CLIENT_ID>"
}'Sample response:
{
"id": "we_abc123",
"toolkit_slug": "slack",
"client_id": "<YOUR_OAUTH_CLIENT_ID>",
"webhook_url": "https://backend.composio.dev/api/v3.1/webhook_ingress/slack/we_abc123/trigger_event",
"data": null,
"created_at": "2026-04-24T10:00:00.000Z"
}Hold on to two values from the response: id (used as <ENDPOINT_ID> below) and webhook_url (you'll paste this into the provider's app dashboard in step 4). The call is idempotent per (toolkit_slug, client_id) within a project — calling it again with the same pair returns the existing endpoint without rotating the URL or wiping the secret.
Step 3: Store the credentials returned by the schema
PATCH each field returned by the schema. For Slack, that's the signing secret and (when applicable) the app-level token.
curl -X PATCH "https://backend.composio.dev/api/v3.1/webhook_endpoints/<ENDPOINT_ID>" \
-H "x-api-key: <YOUR_COMPOSIO_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"data": {
"webhook_signing_secret": "<SIGNING_SECRET>"
}
}'For Slack, the signing secret lives at Slack app → Basic Information → App Credentials → Signing Secret.
Store the credentials before you switch the provider's callback URL in step 4. If the provider posts to the URL without a secret on the endpoint, every request fails with 400, and the provider may auto-disable the endpoint after a window of consecutive failures (Slack: ~36 hours).
For toolkits with additional fields — Slack's app_token, for instance, which is required for direct messages, private channels, and reactions — PATCH them in the same way:
curl -X PATCH "https://backend.composio.dev/api/v3.1/webhook_endpoints/<ENDPOINT_ID>" \
-H "x-api-key: <YOUR_COMPOSIO_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"data": {
"app_token": "xapp-..."
}
}'Generate the Slack app-level token from Slack app → Basic Information → App-Level Tokens with scope authorizations:read.
Step 4: Point the provider's app dashboard at the URL
Paste the webhook_url from step 2 into the provider's app dashboard:
- Slack → Event Subscriptions → Request URL
- Notion → Webhook Endpoints (in the integration's settings)
Composio responds to the provider's verification challenge automatically (Slack url_verification, Notion's verification token, and so on) — you don't write any handshake code on your side.
Step 5: Confirm the endpoint is verified
curl "https://backend.composio.dev/api/v3.1/webhook_endpoints/<ENDPOINT_ID>" \
-H "x-api-key: <YOUR_COMPOSIO_API_KEY>"A populated verified_at timestamp on the response means the provider has completed the handshake. You can now retry the trigger creation call from Using the SDK.
Updating an endpoint
To rotate the signing secret or update any single field, PATCH it (other fields are preserved):
curl -X PATCH "https://backend.composio.dev/api/v3.1/webhook_endpoints/<ENDPOINT_ID>" \
-H "x-api-key: <YOUR_COMPOSIO_API_KEY>" \
-H "Content-Type: application/json" \
-d '{ "data": { "webhook_signing_secret": "<NEW_SECRET>" } }'To replace data wholesale (any field you don't include is cleared), POST to the same URL:
curl -X POST "https://backend.composio.dev/api/v3.1/webhook_endpoints/<ENDPOINT_ID>" \
-H "x-api-key: <YOUR_COMPOSIO_API_KEY>" \
-H "Content-Type: application/json" \
-d '{ "data": { "webhook_signing_secret": "<NEW_SECRET>", "app_token": "<NEW_APP_TOKEN>" } }'The webhook_url is immutable for the lifetime of the endpoint — rotating the signing secret on the provider side is a PATCH on the existing endpoint, not a new one.
To list every endpoint in the current project:
curl "https://backend.composio.dev/api/v3.1/webhook_endpoints" \
-H "x-api-key: <YOUR_COMPOSIO_API_KEY>"