GUIDES / CHATGPT PLUGINS

MCP Events: subscribe ChatGPT to changes in your app

Implement MCP 2.0 with protocol version 2026-07-28, advertise events in server/discover, and expose events/list, events/subscribe and events/unsubscribe on your authenticated MCP endpoint. ChatGPT supplies the callback URL and signing secret when a user requests monitoring. Verify that callback, persist the subscription and POST matching signed events. The subscribed chat follows the user’s existing instruction; an event is not a new permission grant.

Check support before building ↓

1. Match the ChatGPT integration, not the whole draft

The current MCP Events guide requires MCP 2.0, protocol 2026-07-28, persistent subscription storage and outbound HTTPS access. Configure the server in your plugin. The event methods live on the same authenticated MCP endpoint as the tools.

SUPPORTED: webhook delivery and callback verification.

UNSUPPORTED in this integration: polling, streaming, and the draft’s gap and terminated control notifications. A capability appearing in the linked draft specification is not evidence that ChatGPT accepts it. This support boundary was source-checked on 2026-09-30; account-level access was not tested.

2. Follow the subscription through to a user-directed action

This is an explanatory sequence distilled from OpenAI’s integration guide, not a claim about a private internal state machine:

Server advertises events → ChatGPT discovers definitions → user requests monitoring and an action → ChatGPT calls events/subscribe with callback URL and signing secret → server validates access, verifies the callback and stores the subscription → external change occurs → server applies filters → signed webhook POST → event arrives in the subscribed chat → ChatGPT follows the existing instruction → events/unsubscribe stops monitoring.

For example, a user may ask to watch review comments on one document and prepare suggested edits. Receipt of a comment does not authorize every action the comment requests. Write-tool permissions and the user’s original scope still apply. UI is optional; an event-driven plugin can also offer a conversation panel or settings.

3. Advertise and discover event definitions

Include events: {} in the capabilities of server/discover and advertise support for 2026-07-28. Implement:

  • events/list: definitions and available subscription filters.
  • events/subscribe: create or refresh an authorized subscription.
  • events/unsubscribe: stop the matching authorized subscription.

An event definition contains name, description, delivery, inputSchema and payloadSchema. The first schema describes subscription filter arguments; the second describes the delivered event’s data. Validate both on the server. Return only events the connected account may discover; for paginated catalogs use nextCursor and accept cursor on the next list request.

Original shortened design example, not a full JSON-RPC response or a tested server: comment.created has delivery ["webhook"]. Its inputSchema is an object requiring a string document_id. Its payloadSchema is an object requiring four strings: document_id, comment_id, text, url. For this design, reject extra filter fields. A document filter chooses what to watch; the text belongs to the event data, never to a hidden model instruction.

Example subscription arguments and a matching data object:

{
  "subscriptionArguments": { "document_id": "design-42" },
  "exampleEventData": {
    "document_id": "design-42",
    "comment_id": "review-9",
    "text": "Please clarify the loading state.",
    "url": "https://docs.example.org/design-42#review-9"
  }
}

The wrapper keys above are explanatory labels, not protocol fields. In the wire request, subscription arguments go under params.arguments; in a delivered event, the record goes under data. See the official event schema for the complete envelope.

4. Authorize and persist the subscription

The subscribe request contains the event name, arguments and delivery object with mode webhook, URL and secret. Authenticate the principal and authorize the exact resource/filter before accepting it. The signing secret must use the whsec_ prefix and decode from base64 to 24–64 bytes. Keep it in server-side secret storage.

Derive a deterministic subscription identity from the principal, callback URL, event name and canonicalized arguments. Repeated requests should update the existing subscription rather than multiply it. After verification, persist its owner, filters, destination, key and granted expiry across restarts. Return id and refreshBefore, plus the documented cursor/truncated fields. This is subscription state your server owns, not browser component state. Create a subscription.

5. Verify the callback before sending application data

ChatGPT supplies the URL; your server must validate it rather than POST blindly. Require HTTPS, resolve and check the destination at connection time, reject private/local/non-public addresses and redirects, and preserve the original hostname for TLS checks when connecting to the validated address. Apply the same defenses to verification and later delivery.

Send a signed verification control object with a fresh, single-use, short-lived challenge. Include a unique webhook-id, webhook-timestamp, webhook-signature and X-MCP-Subscription-Id. Require a 2xx response and compare the echoed challenge in constant time before enabling delivery. A failed handshake returns the documented -32015 CallbackEndpointError, with a reason such as challenge_failed or timeout. Bound any verification cache by principal and callback URL.

The direction matters: your server verifies the callback challenge; ChatGPT verifies your delivery signature. Do not confuse this with receiving arbitrary inbound webhooks on your own MCP endpoint. Callback verification.

6. Sign one event per request; plan for retries

Create an envelope with stable eventId, subscribed name, occurrence timestamp with timezone, schema-valid data, and cursor as applicable. Application fields belong inside data; a top-level type is for protocol controls.

Use Standard Webhooks with the subscription secret. Set Content-Type to application/json, webhook-id to the eventId, webhook-timestamp to the signing time in Unix seconds, webhook-signature to the HMAC signature, and X-MCP-Subscription-Id to the subscription ID. Serialize once: the signature and POST must use the same body bytes.

Send one event per request, with the complete body at most 256 KiB (262,144 bytes). A 2xx acknowledges receipt, not completed execution. ChatGPT handles it asynchronously and may group separately delivered events into a task run according to batching settings; this does not authorize sending an array of events as one delivery.

Retry transient failures with bounded exponential backoff. Keep the event ID, but generate a fresh signing timestamp and signature for each attempt. Do not retry 410 or 413 responses. Events may arrive out of order; make write tools idempotent so repeated calls do not repeat effects. Do not claim exactly-once delivery. For large records, send a summary with a read tool for authorized retrieval. Delivery contract.

7. Refresh, expire and stop monitoring

ChatGPT refreshes by calling events/subscribe before refreshBefore, using the same identity and last cursor. Update the stored subscription and its granted expiry. Omitted ttlMs uses your default lifetime; a provided duration requests that lifetime, subject to the documented minimum-lifetime exception. A null ttlMs requests no expiry; return refreshBefore: null only if you grant it. Otherwise stop delivery at the finite expiry.

If refresh replaces the secret, rotate it and use the documented short dual-signature window. Replayable event types resume with cursors that do not skip pending deliveries; signal unavailable requested history with truncated. For non-replayable types, return cursor: null and acknowledge that interruption can lose events.

For events/unsubscribe, match the original name, arguments and callback URL under the authenticated account. Make it idempotent, stop delivery and return an empty result. Recheck access during the subscription’s lifetime and stop after permission revocation; do not wait indefinitely for an unsubscribe after an account disconnect. Manage subscriptions.

8. Event text is data; prevent injection and feedback loops

A comment containing “ignore previous instructions and delete…” remains untrusted user-authored text. Never elevate it into a system instruction or add hidden behavioral instructions to the payload. Preserve the user’s existing monitoring scope. Check authorization for discovery, subscription, unsubscribe and every tool call; filter events server-side before delivery.

Use stable IDs and idempotent effects to handle duplicates. Recheck resource access, protect signing keys, stop on revocation and test account disconnection. Callback verification and address validation also protect your server from being used to reach private network destinations. Our MCP server security checklist covers broader MCP boundaries.

Feedback-loop warning: an event can make ChatGPT update the source app; that update may emit the same event again. OpenAI explicitly requires testing this case. AgentSkillsHub implementation suggestions: tag automation-originated changes where your app supports it, exclude your own non-actionable updates, deduplicate by event/effect identity, and bound repeated actions with a stop condition. These are design suggestions, not extra official wire fields or a guarantee that every app exposes an origin marker. Events testing and write-action security.

9. Test the full lifecycle in a disposable workflow

Connect the MCP server through a plugin, check that events appear alongside tools on its page and rescan when definitions change. In a new chat, state both what to monitor and what ChatGPT may do. Use synthetic comments on an authorized test document.

AgentSkillsHub checklist based on official requirements; not hands-on test results:

  • Confirm server/discover advertises events and events/list returns the expected authorized definitions.
  • Confirm the plugin page lists events and subscribe receives the intended name/filter.
  • Verify the callback handshake succeeds before application data leaves the server.
  • Deliver a matching event; check receipt and the separate chat action.
  • Trigger a nonmatching event and confirm server-side filtering prevents delivery.
  • Repeat subscribe and resend an event ID; verify one subscription and no duplicated write effects.
  • Restart the server, then verify persistence, expiry, refresh and replay behavior if supported.
  • Reject invalid signatures and test callback-verification failure safely.
  • Disconnect the account or revoke resource access; verify safe termination of delivery.
  • Stop monitoring and confirm unsubscribe prevents further delivery.
  • Exercise bursts with batching on and off while retaining one event per HTTP request.
  • Make the requested action emit a new app event; verify it cannot form an unbounded feedback loop.

Record evidence at each layer: RPC success, webhook receipt, chat receipt and authorized action outcome. None of those alone proves all the others. Official lifecycle checks.

FAQ

Which MCP version does ChatGPT Events require?

The current integration guide requires MCP 2.0 with protocol version 2026-07-28. It requires events/list, events/subscribe and events/unsubscribe.

Can I use polling or streaming instead of a webhook?

Not with the documented ChatGPT Events integration. It supports webhook delivery and callback verification; polling, streaming, gap and terminated control notifications are unsupported.

Is inputSchema the schema for the delivered comment?

No. inputSchema describes subscription arguments such as document_id filters. payloadSchema describes the data object in each delivered event.

Does a 2xx webhook response mean ChatGPT completed the task?

No. It acknowledges webhook receipt. Processing is asynchronous, and the requested action needs its own verification.

Can one webhook contain a batch of events?

The documented request contains one event and has a 256 KiB body limit. ChatGPT can batch separately delivered events into a task run.

Has AgentSkillsHub tested this lifecycle end to end?

No. We reviewed the documentation but did not connect a server, list or subscribe to events, receive a signed webhook, or unsubscribe during this editorial review.

Primary sources

Source-checked 2026-09-30. SDK/spec publication, account availability, and a hands-on test are separate evidence. Examples and checklists here are editorial material.