Back to Changelog

Apr 27, 2026

Latest updates and announcements

Markdown

Webhook Triggers V2

Webhook Triggers V2 introduces a first-class webhook_endpoints resource with a dedicated ingress URL per OAuth app. V2 is opt-in and scoped to new trigger slugs — existing V1 triggers, URLs, and payload formats are unchanged.

Summary

ChangeAction required
New webhook_endpoints APINone (opt-in)
New ingress path /api/v3.1/webhook_ingress/{toolkit}/{we_*}/trigger_eventNone (opt-in)
Ingress-level signature verificationAutomatic for V2 endpoints
New Slack V2 slugs (SLACK_CHANNEL_MESSAGE_RECEIVED, SLACK_DIRECT_MESSAGE_RECEIVED, SLACK_MESSAGE_REACTION_ADDED)Opt-in
V1 ingress path and all legacy trigger slugsNone — unchanged

What's in V2

Dedicated endpoint per OAuth app. Each webhook_endpoint is keyed by (toolkit_slug, project_id, client_id) and exposes its own URL containing a random we_* identifier:

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

This replaces V1's shared /handle URL per toolkit. Events arriving on a V2 URL are fanned out only to trigger instances on that endpoint's project.

Signature verification at ingress. Composio cryptographically verifies every V2 request against the signing secret stored on the endpoint, using HMAC-SHA256, Ed25519, or shared-token matching depending on the toolkit. Unsigned or tampered payloads are rejected, so third parties cannot spoof events onto your triggers. For providers that sign a request timestamp (e.g. Slack), replay protection additionally rejects requests whose timestamp falls outside the allowed skew window.

Per-user authorization for app-level events. On toolkits with per-user visibility — Slack being the first — V2 uses an app-level token (for Slack, an xapp-… token with authorizations:read) to resolve which connected users are authorized for a given event, so only those users' triggers fire. This underpins a new class of triggers for private channels, direct messages, and other per-recipient events, each routed only to the user the event was intended for.

Automatic handshakes and cleanup. Composio responds to provider verification challenges — Slack url_verification, Asana X-Hook-Secret, Outlook validation tokens, and so on — so callback URLs verify without any custom code. User-level webhooks are also deregistered on the provider side when the trigger is deleted.

API reference

All endpoints require a project API key (x-api-key).

MethodPathPurpose
GET/api/v3.1/webhook_endpoints/schema?toolkit_slug={slug}List required setup fields for a toolkit
POST/api/v3.1/webhook_endpointsCreate an endpoint (idempotent per toolkit_slug + client_id within a project)
GET/api/v3.1/webhook_endpointsList endpoints in the current project
GET/api/v3.1/webhook_endpoints/{id}Get a single endpoint, including verified_at
POST/api/v3.1/webhook_endpoints/{id}Replace setup_data
PATCH/api/v3.1/webhook_endpoints/{id}Update one or more fields in setup_data

Provider events are posted to:

MethodPath
POST/api/v3.1/webhook_ingress/{toolkit_slug}/{webhook_endpoint_nano_id}/trigger_event

Migration walkthrough (Slack)

Slack is the first toolkit on V2. The same flow applies to every toolkit added later.

1. Discover required fields

curl "https://backend.composio.dev/api/v3.1/webhook_endpoints/schema?toolkit_slug=slack" \
  -H "x-api-key: YOUR_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
    }
  }
}

2. Create the endpoint

curl -X POST "https://backend.composio.dev/api/v3.1/webhook_endpoints" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "toolkit_slug": "slack", "client_id": "YOUR_SLACK_CLIENT_ID" }'

Sample response:

{
  "id": "we_abc123",
  "toolkit_slug": "slack",
  "client_id": "YOUR_SLACK_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 in the next steps) and webhook_url (the URL you'll paste into your Slack app dashboard in step 5). The call is idempotent — calling it again with the same toolkit_slug and client_id returns the existing endpoint.

3. Store the signing secret

curl -X PATCH "https://backend.composio.dev/api/v3.1/webhook_endpoints/ENDPOINT_ID" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "data": { "webhook_signing_secret": "YOUR_SIGNING_SECRET" } }'

Store the signing secret before switching the provider's callback URL. If Slack posts to a V2 URL without a secret available, every request fails with 400 and Slack may auto-disable the endpoint after ~36 hours of failures.

4. Add an app-level token for private-scope events

The app-level token is what lets Composio resolve per-user authorization for Slack's private-scope events.

  • SLACK_DIRECT_MESSAGE_RECEIVED always requires it.
  • SLACK_CHANNEL_MESSAGE_RECEIVED requires it for events from private channels and multi-person DMs; public-channel messages are delivered without it.

Skip this step if you only need public-channel messages today — you can add the token later.

curl -X PATCH "https://backend.composio.dev/api/v3.1/webhook_endpoints/ENDPOINT_ID" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "data": { "app_token": "xapp-..." } }'

Generate the token from Slack app → Basic Information → App-Level Tokens with scope authorizations:read.

5. Point your Slack app at the V2 URL

Set Event Subscriptions → Request URL in your Slack app to the webhook_url returned in step 2. Composio verifies the endpoint with Slack automatically — no extra action needed on your side.

6. Create V2 trigger instances

Three new Slack triggers go live on V2 today, with more to follow on V2 — both for Slack and for other toolkits. The table below maps your existing V1 slugs to the new V2 equivalents:

V1 slug (still supported)V2 replacementapp_token required?
SLACK_RECEIVE_MESSAGESLACK_CHANNEL_MESSAGE_RECEIVEDOnly for private channels and multi-person DMs; public channels work without it
SLACK_RECEIVE_DIRECT_MESSAGESLACK_DIRECT_MESSAGE_RECEIVEDAlways
SLACK_REACTION_ADDEDSLACK_MESSAGE_REACTION_ADDEDAlways (reactions don't carry channel type, so per-user authz runs unconditionally)
curl -X POST "https://backend.composio.dev/api/v3.1/trigger_instances/SLACK_CHANNEL_MESSAGE_RECEIVED/upsert" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "connected_account_id": "CONNECTED_ACCOUNT_ID",
    "trigger_config": {}
  }'

Heads up: OAuth apps are project-scoped on V2

Action required if you share a single OAuth app across multiple Composio projects or organizations today. V2 ties each OAuth app (client_id) to exactly one project. Before moving such an app to V2, either consolidate to a single project or register separate OAuth apps per project.

The provider dashboard accepts only one callback URL per OAuth app, and each V2 URL resolves to exactly one project — so a single OAuth app cannot feed multiple projects on V2.

The signing secret also lives on the endpoint, so the reverse holds too: two different OAuth apps cannot share one V2 endpoint — Composio only verifies signatures against the secret stored on that endpoint, so requests signed by a different OAuth app would be rejected.

In return, every project graduates to a first-class webhook tenant on V2 — with its own:

  • Ingress rate-limit and backpressure budget
  • Clean fan-out — events reach only that project's trigger instances
  • Signing secret and app-level token, scoped to this project alone
  • Per-project metering and billing

Your V1 endpoints are unaffected. Any OAuth app you leave on V1 continues to work across projects exactly as it does today.

Backward compatibility

Everything you have today keeps working — same URLs, same trigger slugs, same payload shapes, no data migration.

  • The V1 ingress URL (/api/v3/trigger_instances/{toolkit}/{project_id}/handle) is not deprecated.
  • Every existing trigger slug — SLACK_RECEIVE_*, SLACKBOT_*, and the equivalent legacy slugs on other toolkits — continues to route through V1.

You only need to set up a V2 webhook endpoint in two cases:

  1. You want to use one of the new V2 trigger slugs (e.g. SLACK_CHANNEL_MESSAGE_RECEIVED, SLACK_DIRECT_MESSAGE_RECEIVED). A V2 endpoint is required at setup; without one, the upsert returns 400.
  2. You want to move an OAuth app that's currently shared across multiple projects or organizations onto V2. Pick the project that should own it on V2 and set up an endpoint there, or register separate OAuth apps per project (see Heads up: OAuth apps are project-scoped on V2).

Even outside these two cases, we recommend moving your OAuth apps to V2 at your own pace. Every endpoint you migrate inherits the signature verification, ingress isolation, and per-user authorization called out above.