cd /news/agent-protocols/mcp-events-in-chatgpt-why-an-event-s… · home › topics › agent-protocols › article
[ARTICLE · art-143315] src=workos.com ↗ pub= topic=agent-protocols verified=true sentiment=· neutral

MCP Events in ChatGPT: Why an event subscription is a credential

OpenAI said at its September 29 DevDay that ChatGPT is adding draft MCP Events support, letting MCP servers notify the client via `events/subscribe` and webhook delivery, with the feature listed for all plans on a platform OpenAI describes as reaching 1.2 billion weekly users. The draft extension, incubated in `modelcontextprotocol/experimental-ext-triggers-events` under a design sketch authored by Peter Alexander at Anthropic and dated 2026-02-19, has no filed SEP and no named champion, while OpenAI's guide requires MCP 2.0 and protocol version `2026-07-28` and omits polling, streaming, and the draft's `gap` and `terminated` control notifications. The open question is that an event subscription is a durable credential — a stored record naming a user, a public webhook destination, and a `whsec_` signing secret — that keeps authorizing pushes after the token that created it expires, with no defined revalidation interval.

read10 min views1 publishedOct 1, 2026
MCP Events in ChatGPT: Why an event subscription is a credential
Image: Workos (auto-discovered)

ChatGPT is adding draft MCP Events support. Why a subscription is a credential, how revocation works, and what your MCP server must store.

MCP Events is a draft extension to the Model Context Protocol that lets an MCP server notify a client, such as ChatGPT, when something changes in a connected app. The client subscribes with events/subscribe, and your server stores that subscription and delivers matching events to a webhook until the subscription expires or is ended.

At DevDay on September 29, OpenAI said it is "adding support for the proposed MCP Events specification, so plugins can start automations when something happens in a connected app." The feature is listed for all plans, and the same recap describes ChatGPT as a platform reaching "our collective 1.2B weekly users." In that post, the words "proposed MCP Events specification" link to the MCP Triggers and Events working group charter, whose Active Work Items table lists "SEP: Events in MCP v1 RFC" with status Ideating, target date End April, and champion TBD.

Implementations running ahead of ratification is normal and mostly healthy. The part that deserves attention is what the draft asks your server to store. The draft doesn't call it one, but an event subscription is a credential: a durable record, created under one user's access token, that authorizes your MCP server to push that user's data at an agent long after the token that created it has expired. What neither the draft nor the ChatGPT integration pins down is how often you have to check that it should still exist.

What is ChatGPT actually implementing? #

The charter page is thin, and dated. Its changelog has exactly one entry, dated 2026-03-24: "Initial charter." Both of its work items, the v1 RFC and reference implementations in Tier-1 SDKs, still carry an End April target with no champion named. The working group is led by Clare Liguori at AWS and Peter Alexander at Anthropic.

The incubation repository tells a different story than the charter table. The charter names modelcontextprotocol/experimental-ext-triggers-events as the group's incubation space, and that repo holds a design sketch, docs/design-sketch-proposal.md, marked "Status: Draft proposal," with Peter Alexander as author and a date of 2026-02-19. Implementers are already filing field reports against it as issues. The README is blunt about standing: the contents are exploratory and "do not represent official MCP specifications or recommendations."

So the champion field is a stale page, not an absent effort. There is an author, a long and specific design document, and implementers reporting back. What there is not is a filed SEP, which is the charter's own stated success criterion: "An accepted SEP defining the trigger/callback mechanism and its subscription lifecycle." Subscription lifecycle is explicitly in scope for the group and explicitly unfinished.

ChatGPT implements a slice of that unfinished document. OpenAI's MCP Events guide requires MCP 2.0, protocol version 2026-07-28, and supports webhook delivery and callback verification from the draft. Polling, streaming, and the draft's gap and terminated control notifications are not supported. That last one matters.

What does events/subscribe actually create? #

When a user tells ChatGPT to watch something, ChatGPT calls your server with the event name, filter arguments, and a webhook destination:

Before you accept it, OpenAI's guide tells you to check that the user is authorized for that event and those arguments, validate the arguments and the whsec_ secret, verify the callback URL with a signed challenge, and then store the subscription along with its owner, filters, callback URL, signing secret, and expiration. That storage step is the whole argument. You now hold a persistent row that names a user, a destination on the public internet, and a secret.

Compare it to the token that authorized the call. Under the 2026-07-28 authorization spec, servers "MUST validate that access tokens were issued specifically for them," authorization "MUST be included in every HTTP request," and "Invalid or expired tokens MUST receive a HTTP 401 response." Clients "MUST NOT assume refresh tokens will be issued." Short-lived, narrow in scope, and re-checked on every request.

The subscription inherits none of that. The design sketch keys a webhook subscription on the tuple (principal, delivery.url, name, arguments), where principal is "the server's canonical identifier for the authenticated subject (e.g., OAuth sub...)." The tuple doesn't include the token itself, its scopes, or its expiry. And the lifetime is negotiable all the way to forever: ttlMs: null requests a subscription with no expiry, and a server that grants it returns refreshBefore: null.

How is an MCP event subscription revoked? #

The design sketch is clear about the obligation and vague about the cadence. At subscribe time, the principal MUST be authenticated and authorized. At delivery time: "The server SHOULD periodically re-verify permissions. If the user's access is revoked (e.g., removed from a Slack channel)," the server terminates the subscription. OpenAI's guide restates the same duty as yours to discharge: "Retain subscription state for the lifetime you grant... Recheck the user's access during the subscription's lifetime and stop delivery if access is revoked."

Periodically is doing a lot of work in that sentence. There is no interval, no MUST, and no conformance test behind it.

Then there is the signal itself. In the draft, each delivery mode has its own way to say "stop." For webhooks it is a signed {"type":"terminated",...} envelope POSTed to the callback URL. After that the subscription no longer exists, so a later refresh "is a fresh subscribe and returns -32012 Forbidden if the cause of termination still applies." That envelope is the protocol's way of telling the agent "this stopped, and here is why." It is also one of the two control notifications ChatGPT's integration does not support.

Follow it through. Access is revoked upstream, your server stops delivering, and ChatGPT learns nothing in band. The only remaining signal is the refresh: the client re-calls events/subscribe before refreshBefore, the subscription is gone, and that call fails. Which means the TTL you grant is your revocation window. The design sketch says that "grants from a few minutes up to about a day cover most deployments," because refresh traffic "scales as O(1/TTL)" while storage and reclamation grow with the TTL. Seen that way, the tradeoff is also a security dial, and a day is a long time to leave a client believing a dead subscription is alive.

Grant no expiry and the dial breaks off. There is no refresh, so there is no error, and the one envelope that could have said so is unsupported. The design sketch already names this failure, as its argument for why a no-expiry grant must be persisted across restarts: "a client that never refreshes will never detect (or repair) a silently dropped one."

The reasonable objection is that none of this leaks data. A subscription your server has stopped honoring delivers nothing; the worst case is a user whose automation quietly stopped. True, and that is the failure I worry about least. The expensive direction is the other one: delivery-time rechecking is a SHOULD with no cadence, so the path of least effort is a server that authorizes at subscribe time and never looks again.

Put a clock on that. IT revokes a departing employee's access to the customer-contracts folder on Monday morning. Your MCP server granted their document.updated subscriptions a 24-hour TTL, so the next refresh lands Tuesday morning. If your server re-checks access on delivery, nothing ships after Monday and the client finds out Tuesday. If it only checked at subscribe time, which is all the draft strictly requires, every contract edit between Monday and Tuesday is POSTed to a callback URL that still answers, lands in a chat thread the former employee's automation set up, and leaves no trace in your audit log beyond a 200 on a webhook delivery. That is a conformant implementation of a draft that leaves the interval to you.

Who can list a user's event subscriptions? #

The draft is deliberate about where subscription state lives: "The client owns all subscription state across all three delivery modes. There is no server-side subscription listing method." For audit, it points enterprises at the client: "Enterprise agent runtimes can inspect the client SDK's subscription registry and log all event-triggered actions." For a ChatGPT plugin, that registry sits on OpenAI's side of the wire, where neither you nor your customer's admin can query it.

Your side is told to derive a deterministic subscription ID from the subscription key and to hold the record for the full lifetime you granted, across restarts. So you are the only party who can answer "what is this user currently subscribed to," and nothing in the protocol asks you to expose it. Store enough to answer it:

Two indexes, two jobs. The user_id index is what your offboarding path calls to end every subscription a departing user created. The reverified_at index is what turns "periodically" into a number: sweep anything older than your chosen interval, re-run the same authorization check you ran at subscribe time, and delete what fails.

Ending a subscription from your side is easy: delete the row and stop delivering. Telling the client is the hard part. The draft's notice for that is the terminated envelope, which ChatGPT doesn't support, so ChatGPT learns only when its next refresh fails. For a no-expiry subscription there is no next refresh, and only the client can send events/unsubscribe. If your offboarding already runs on SCIM deprovisioning, wiring the per-user delete into the same handler is the cheapest part of this whole post.

What should your server verify before pushing data to an agent? #

The draft and OpenAI's guide between them specify a real security surface. The parts that are not negotiable:

  • Require an authenticated principal. "events/subscribe andevents/unsubscribe MUST be called with an authenticated principal," and calls that fail authorization get-32012 Forbidden .
  • Verify the endpoint before the first real delivery. In the draft's words, HMAC stops forgery, not flooding: a server "MUST NOT begin delivering to a callback URL until the endpoint's intent to receive deliveries is confirmed." The options are a challenge handshake, an allowlist, prior out-of-band verification, or a receiver-published/.well-known/mcp-webhook-receiver.json , with verification cached per(principal, url) . For ChatGPT, your server sends a signed challenge to the callback URL and validates the echoed response, using a constant-time comparison, before it activates delivery.
  • Run the SSRF checks at delivery time. Callback URLs "MUST usehttps:// ." Resolve and validate the destination address on each connection, block private, local, and other non-public addresses, never follow redirects, and apply all of it to verification requests as well as deliveries.
  • Keep payloads minimal. Event payloads carry the same injection risk as tool results. OpenAI's guide says to send a summary and expose a read tool for the full record, to treat user-authored text as data, and not to add instructions telling the model how to behave inside the payload.
  • Make writes idempotent. Events can arrive out of order, so repeated calls must not duplicate changes. Deliveries cap at 256 KiB, and410 and413 responses are not retried.
  • Authorize at action time, not at receipt. "Event receipt does NOT constitute authorization to act." The tool call the agent makes in response goes through your normal checks, the same ones that gateMCP tool calls beyond OAuth scopes .

Everything above is in the documents. The two things that are not: how often you recheck access, and how a user or an admin sees what their account is pushing.

Pick your revocation window on purpose #

Subscription lifecycle is the exact thing the working group chartered itself to specify and has not shipped a SEP for yet. Until it does, three decisions are yours, and defaults will decide them badly.

Grant short finite TTLs and refuse ttlMs: null, because the TTL is the interval at which a revoked subscription becomes visible to the client. Re-verify access on a schedule you can state in a sentence, not "periodically." And keep the per-user index above, because without it, offboarding a user means assuming their subscriptions died rather than confirming it.

That is a few days of work on top of an integration you were going to build anyway, and it is the difference between an event subscription that behaves like a session and one that behaves like an OAuth grant nobody can see.

── more in #agent-protocols 4 stories · sorted by recency
── more on @openai 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/mcp-events-in-chatgp…] indexed:0 read:10min 2026-10-01 · —