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 are in preview. Breaking changes may happen without notice.
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. Only webhook delivery is supported today — events/poll and events/stream from the proposal are not implemented.
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 or via Connect.
- A
SMITHERY_API_KEY from your namespace settings.
- An HTTPS endpoint that can receive webhooks.
1. Create a connection
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
A single call carries 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:
WEBHOOK_SECRET="whsec_$(openssl rand -base64 32)"
curl -X POST "https://smithery.run/my-app/notion/.triggers/page.updated" \
-H "Authorization: Bearer $SMITHERY_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"params\": { \"workspace_id\": \"w_123\" },
\"delivery\": {
\"url\": \"https://my-app.example.com/events\",
\"secret\": \"$WEBHOOK_SECRET\"
}
}"
{
"id": "sub_a3f1c8e2b0d49f7e",
"refreshBefore": "2026-04-22T13:00:00.000Z"
}
The subscription is soft state with a mandatory TTL. Re-call this same endpoint with the same key (name, params, delivery.url) before refreshBefore to keep it alive — see Refreshing.
3. Handle events at your endpoint
Verify each delivery against the secret you supplied at subscribe time, then dispatch on event.name:
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
# List trigger types on a connection
curl "https://smithery.run/my-app/notion/.triggers" \
-H "Authorization: Bearer $SMITHERY_API_KEY"
# Get one trigger's schema
curl "https://smithery.run/my-app/notion/.triggers/page.updated" \
-H "Authorization: Bearer $SMITHERY_API_KEY"
{
"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" }
}
}
}
From an active MCP session, call ai.smithery/events/list:{
"jsonrpc": "2.0",
"id": 1,
"method": "ai.smithery/events/list"
}
The response mirrors the REST payload — a catalog of { name, description, delivery, inputSchema, payloadSchema }.
Subscribing
curl -X POST "https://smithery.run/my-app/notion/.triggers/page.updated" \
-H "Authorization: Bearer $SMITHERY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"params": { "workspace_id": "w_123" },
"delivery": {
"url": "https://my-app.example.com/events",
"secret": "whsec_<base64 of 24-64 random bytes>"
}
}'
{
"id": "sub_a3f1c8e2b0d49f7e",
"refreshBefore": "2026-04-22T13:00:00.000Z"
}
{
"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>"
}
}
}
{
"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.
# Refresh — same call as the original subscribe
curl -X POST "https://smithery.run/my-app/notion/.triggers/page.updated" \
-H "Authorization: Bearer $SMITHERY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"params": { "workspace_id": "w_123" },
"delivery": {
"url": "https://my-app.example.com/events",
"secret": "whsec_<same-or-rotated-secret>"
}
}'
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 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.
curl -X DELETE "https://smithery.run/my-app/notion/.triggers/page.updated" \
-H "Authorization: Bearer $SMITHERY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"params": { "workspace_id": "w_123" },
"delivery": { "url": "https://my-app.example.com/events" }
}'
{
"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.
API reference
All paths are relative to https://smithery.run. Requests require Authorization: Bearer $SMITHERY_API_KEY.
REST endpoints
| Method | Path | Body | Purpose |
|---|
GET | /{ns}/{conn}/.triggers | — | List trigger types on a connection. |
GET | /{ns}/{conn}/.triggers/{name} | — | Get a trigger’s schema. |
POST | /{ns}/{conn}/.triggers/{name} | { params, delivery: { url, secret } } | Subscribe or refresh. Returns { id, refreshBefore }. |
DELETE | /{ns}/{conn}/.triggers/{name} | { params, delivery: { url } } | Unsubscribe. |
MCP methods
Available on any connection advertising the ai.smithery/events extension during initialize. Method and field names track the MCP Events proposal.
| Method | Params | Result |
|---|
ai.smithery/events/list | — | { events: [{ name, description, delivery, inputSchema, payloadSchema }] } |
ai.smithery/events/subscribe | { name, params, delivery: { mode: "webhook", url, secret } } | { id, refreshBefore } |
ai.smithery/events/unsubscribe | { name, params, delivery: { url } } | {} |
Learn more