Using Triggers

Creating triggers

Markdown

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

  1. Navigate to Auth Configs and select the auth config for the relevant toolkit
  2. Navigate to Active Triggers and click Create Trigger
  3. Select the connected account for which you want to create a trigger
  4. 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.
  5. Click Create Trigger
Creating a GitHub Star Added trigger from the dashboard

Configuring the webhook endpoint

Some webhook triggers require a webhook endpoint registered with the provider before they can fire. With Composio-managed OAuth, this is already done for you. You only run the steps below when you bring your own OAuth app and the trigger type's requires_webhook_endpoint_setup flag is true.

Each OAuth app you bring gets its own ingress URL within a project:

https://backend.composio.dev/api/v3.1/webhook_ingress/{toolkit_slug}/{we_xxx}/trigger_event

A single OAuth app can serve at most one Composio project, since providers accept only one callback URL per OAuth app and each ingress URL routes to a single project. The upside: every project becomes its own webhook tenant with isolated ingress rate limits, its own signing secret and app-level token, clean fan-out (events only reach that project's triggers), and per-project metering. Repeat verification handshakes are rejected after the endpoint is verified, so the signing secret can't be silently swapped.

Provider events are verified at ingress against the endpoint's signing secret before any trigger fires — HMAC-SHA256 for Slack, Ed25519 or shared-token matching for others. For providers that sign a request timestamp, replay protection additionally rejects requests outside the allowed skew window. Unsigned or tampered requests get a 400 at ingress, so third parties cannot spoof events onto your triggers.

Sharing one OAuth app across projects? Consolidate to a single project or register separate OAuth apps per project before continuing.

The steps below use Slack. Other toolkits follow the same flow with different setup fields.

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