> ## Documentation Index
> Fetch the complete documentation index at: https://smithery.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Triggers

> Receive webhooks when things happen in your connected services.

<Warning>
  Triggers are in preview. Breaking changes may happen without notice.
</Warning>

Triggers let your app react to events from the services you've connected through Smithery — a Notion page update, a GitHub push, a Slack message — without the user being present. Each connection exposes a catalog of trigger types declared by the underlying MCP server. Your app subscribes through Smithery; the upstream MCP server delivers events as signed HTTPS webhooks **directly to your endpoint**.

Smithery's wire protocol tracks the [MCP Events proposal](https://github.com/modelcontextprotocol/experimental-ext-triggers-events/pull/1). Only **webhook** delivery is supported today — `events/poll` and `events/stream` from the proposal are not implemented.

```mermaid theme={null}
flowchart LR
    subgraph App[Your application]
        E[Webhook endpoint]
    end
    App -- subscribe --> S[Smithery]
    S -- events/subscribe --> M[MCP Server]
    M -- register --> Up[Upstream service]
    Up -- event --> M
    M -- signed HTTPS POST --> E
```

Smithery proxies subscribe/unsubscribe calls through to the upstream MCP server (handling auth on your behalf). Once subscribed, **events flow directly from the upstream MCP server to your endpoint** — Smithery is not in the delivery path.

Triggers are to events what tools are to actions:

| Tools                         | Triggers                                         |
| ----------------------------- | ------------------------------------------------ |
| Synchronous action you invoke | Event that fires when something happens upstream |
| Request/response              | Signed HTTPS POST to your URL                    |
| `tools/list`, `tools/call`    | `events/list`, `events/subscribe`                |

Both are declared by the MCP server and live under the same connection.

## Quick start

You'll need:

* A Smithery namespace (e.g. `my-app`) — created on the [dashboard](https://smithery.ai) or via [Connect](/docs/use/connect).
* A `SMITHERY_API_KEY` from your namespace settings.
* An HTTPS endpoint that can receive webhooks.
* An active MCP session to the connection (via the [SDK](/docs/use/connect) or CLI).

### 1. Create a connection

```bash theme={null}
curl -X PUT "https://smithery.run/my-app/notion" \
  -H "Authorization: Bearer $SMITHERY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "mcpUrl": "https://server.smithery.ai/notion" }'
```

### 2. Subscribe to a trigger

Over MCP, call `ai.smithery/events/subscribe` with what to listen for (`name` + `params`), where to deliver (`delivery.url`), and the Standard Webhooks secret used to sign deliveries (`delivery.secret`). Generate the secret on your side first:

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "ai.smithery/events/subscribe",
  "params": {
    "name": "page.updated",
    "params": { "workspace_id": "w_123" },
    "delivery": {
      "mode": "webhook",
      "url": "https://my-app.example.com/events",
      "secret": "whsec_<base64 of 24-64 random bytes>"
    }
  }
}
```

```json theme={null}
{
  "id": "sub_a3f1c8e2b0d49f7e",
  "refreshBefore": "2026-04-22T13:00:00.000Z"
}
```

The subscription is soft state with a mandatory TTL. Re-call subscribe with the same key (`name`, `params`, `delivery.url`) before `refreshBefore` to keep it alive — see [Refreshing](#refreshing).

### 3. Handle events at your endpoint

Verify each delivery against the secret you supplied at subscribe time, then dispatch on `event.name`:

```typescript theme={null}
import { Webhook } from "standardwebhooks"

const WEBHOOK_SECRET = process.env.SMITHERY_WEBHOOK_SECRET!

app.post('/events', async (req, res) => {
  let event
  try {
    event = new Webhook(WEBHOOK_SECRET).verify(
      req.rawBody,
      req.headers as Record<string, string>,
    )
  } catch {
    return res.status(400).end()
  }

  if (event.name === 'page.updated') {
    await handlePageUpdated(event.data)
  }

  res.status(204).end()
})
```

## Discovering triggers

From an active MCP session, call `ai.smithery/events/list`:

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "ai.smithery/events/list"
}
```

```json theme={null}
{
  "events": [
    {
      "name": "page.updated",
      "description": "Fires when a page in the watched workspace is updated.",
      "delivery": ["webhook"],
      "inputSchema": {
        "type": "object",
        "properties": {
          "workspace_id": { "type": "string" }
        },
        "required": ["workspace_id"]
      },
      "payloadSchema": {
        "type": "object",
        "properties": {
          "page_id": { "type": "string" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      }
    }
  ]
}
```

## Subscribing

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "ai.smithery/events/subscribe",
  "params": {
    "name": "page.updated",
    "params": { "workspace_id": "w_123" },
    "delivery": {
      "mode": "webhook",
      "url": "https://my-app.example.com/events",
      "secret": "whsec_<base64 of 24-64 random bytes>"
    }
  }
}
```

```json theme={null}
{
  "id": "sub_a3f1c8e2b0d49f7e",
  "refreshBefore": "2026-04-22T13:00:00.000Z"
}
```

| Field             | Description                                                                                                                                      |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name`            | Trigger name from the catalog.                                                                                                                   |
| `params`          | Trigger-specific input matching the trigger's `inputSchema`.                                                                                     |
| `delivery.url`    | Your HTTPS webhook endpoint. The upstream MCP server POSTs events directly here.                                                                 |
| `delivery.secret` | Standard Webhooks secret you generate: `whsec_` followed by base64 of 24–64 random bytes. The upstream MCP server signs each delivery with this. |

### Subscription identity

A subscription is keyed by `(principal, delivery.url, name, params)`. Two calls with different `params` or a different `delivery.url` are different subscriptions. To watch two Notion workspaces, subscribe twice — once per `workspace_id`.

To fan in events from many triggers (or many connections) into one endpoint, subscribe each trigger with the same `delivery.url`. Your receiver routes deliveries by the `X-MCP-Subscription-Id` header.

### Refreshing

Webhook subscriptions expire on TTL. Re-call subscribe with the same key before `refreshBefore` to reset it. If you stop refreshing, the subscription expires and the upstream MCP server stops delivering — no explicit unsubscribe is required.

Supplying a new `delivery.secret` on refresh rotates the signing key.

## Receiving events

The upstream MCP server POSTs each event directly to your subscription's `delivery.url`:

```
POST https://my-app.example.com/events
Content-Type: application/json
webhook-id: evt_01HW...
webhook-timestamp: 1739980800
webhook-signature: v1,<base64 HMAC-SHA256(secret, "id.timestamp.body")>
X-MCP-Subscription-Id: sub_a3f1c8e2b0d49f7e

{
  "eventId": "evt_01HW...",
  "name": "page.updated",
  "timestamp": "2026-04-22T12:00:00.000Z",
  "data": {
    "page_id": "...",
    "updated_at": "2026-04-22T12:00:00.000Z"
  }
}
```

| Field       | Description                                                                      |
| ----------- | -------------------------------------------------------------------------------- |
| `eventId`   | Stable event identifier; matches the `webhook-id` header. Use for deduplication. |
| `name`      | Trigger name, e.g. `page.updated`.                                               |
| `timestamp` | ISO 8601 timestamp from the upstream event.                                      |
| `data`      | Payload matching the trigger's `payloadSchema`.                                  |

### Signature verification

The upstream MCP server signs every delivery with the [Standard Webhooks](https://www.standardwebhooks.com/) HMAC profile, using the `delivery.secret` you supplied at subscribe time.

| Header                  | Value                                                                          |
| ----------------------- | ------------------------------------------------------------------------------ |
| `webhook-id`            | Unique event identifier; dedupe on this.                                       |
| `webhook-timestamp`     | Unix seconds; reject deliveries older than \~5 minutes.                        |
| `webhook-signature`     | `v1,<base64 HMAC-SHA256(secret, "webhook-id.webhook-timestamp.body")>`.        |
| `X-MCP-Subscription-Id` | Subscription id. Use it to look up the correct secret before parsing the body. |

Verify against the **raw** request body, not a re-serialized JSON object. Off-the-shelf Standard Webhooks libraries (e.g. Svix) work without modification.

During secret rotation, `webhook-signature` may carry multiple space-delimited `v1,` signatures; compliant verifiers accept the delivery if any one verifies.

### Delivery semantics

* **At-least-once** — dedupe on `webhook-id`.
* The upstream MCP server retries failures with exponential backoff and gives up on persistent non-retryable errors.
* Your handler should be idempotent and return `2xx` only after the event is durably accepted.

## Unsubscribing

Unsubscribing is eager cleanup — subscriptions also expire naturally on TTL if you stop refreshing. Both forms supply the subscription key (`name`, `params`, `delivery.url`); the `id` is not accepted as input.

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "ai.smithery/events/unsubscribe",
  "params": {
    "name": "page.updated",
    "params": { "workspace_id": "w_123" },
    "delivery": { "url": "https://my-app.example.com/events" }
  }
}
```

Unsubscribing also deregisters any upstream webhook the trigger had registered.

## MCP methods

Available on any connection advertising the `ai.smithery/events` extension during `initialize`. Method and field names track the [MCP Events proposal](https://github.com/modelcontextprotocol/experimental-ext-triggers-events/pull/1).

| Method                           | Params                         | Result                                                                  |
| -------------------------------- | ------------------------------ | ----------------------------------------------------------------------- |
| `ai.smithery/events/list`        | none                           | Event catalog (name, description, delivery, inputSchema, payloadSchema) |
| `ai.smithery/events/subscribe`   | name, params, webhook delivery | Subscription id and refreshBefore                                       |
| `ai.smithery/events/unsubscribe` | name, params, delivery URL     | Empty object                                                            |

## Learn more

* [Build triggers into an MCP server](/docs/build/triggers) — for server authors
* [Connect](/docs/use/connect) — manage MCP connections
* [Standard Webhooks spec](https://www.standardwebhooks.com/)
