ChatGPT MCP Events: Build Your First Event-Triggered Plugin Automation, Step by Step
ChatGPT MCP Events let ChatGPT subscribe to changes in your systems and act on them. This guide covers the build as an Advertise-Subscribe-Verify-Deliver (ASVD) loop. You advertise the capability and events/list, make subscriptions idempotent, verify callbacks, sign deliveries as Standard Webhooks and test before release. It also points out where the draft spec and the SDKs are still changing.
With MCP Events, ChatGPT subscribes to updates from your MCP server, and users choose what to monitor and what ChatGPT should do when an update arrives. If your team already runs an MCP server or a SaaS API, you may find that a connector can now start an automation on its own instead of waiting for a prompt.
We'll go through the build in the order you will write the code. We call it the Advertise-Subscribe-Verify-Deliver (ASVD) loop: advertise the events you emit, make subscriptions idempotent, verify every callback before it goes live, then deliver signed events with retries that know when to stop. We'll also flag where the specification is still a draft, because some of those unsettled details end up in your code.
What MCP Events Are and What ChatGPT Actually Supports
OpenAI's developer documentation keeps the description short: ChatGPT subscribes to updates from your MCP server, such as new messages, content updates or status changes, and the user decides what happens next. The draft design sketch aims higher. It wants an MCP client to subscribe to events in an upstream system, such as a Slack message, a GitHub push or a PagerDuty incident, so the agent can react without the user being present.
The design fact that matters most is what ChatGPT leaves out. ChatGPT supports webhook delivery and callback verification from the draft MCP Events specification. Polling, streaming, and the draft's gap and terminated control notifications are not supported by this integration.
| Draft spec feature | Supported by ChatGPT | What to build for ChatGPT |
|---|---|---|
| Webhook delivery | Yes | Build it, sign it, retry it |
| Callback verification | Yes | Build it before activating any subscription |
| Polling | No | Skip for this integration |
| Streaming (push mode) | No | Skip for this integration |
| Gap and terminated control notifications | No | Skip for this integration |
Svix, a webhook infrastructure company, calls ChatGPT the first major client to support MCP Events. It also notes that your server delivers events to ChatGPT as Standard Webhooks. A third-party build guide on DEV Community says ChatGPT support began at OpenAI DevDay on September 29, 2026, at protocol version 2026-07-28. Confirm both in OpenAI's own documentation before you hard-code anything around them.
So build for webhooks and nothing else. Polling and push-stream code paths would need upkeep for as long as they exist, and they do nothing for this integration.
Before You Build: SDK Reality and Spec Status
After DevDay, a developer on the OpenAI Developer Community reported that the official Python SDK (mcp 2.2.0) did not yet support MCP Events, and that the TypeScript SDK did not either. That may have changed, so read the current release notes. We would still budget time for some hand-written plumbing.
Go is in better shape. An open Go SDK issue notes that its custom method support already lets you implement events/list, events/subscribe and events/unsubscribe. What's missing is a way to advertise the top-level events capability.
The spec itself is still moving. The working group charter says that clients currently learn about server-side updates by polling or holding an SSE connection open, and that the group plans to specify a standardized callback mechanism so servers can push notifications instead. On 2026-10-16 the group was renamed the Tasks and Events WG, and it took over the Tasks scope from the Agents WG. Expect more revisions.
Our advice is to write thin custom handlers now and keep them walled off. Give the JSON-RPC method handlers their own module, and do the same for subscription storage and for signing and delivery. When SDK support lands, you swap out the protocol glue and your business logic stays put.
ASVD Step 1 (Advertise): Declare the Capability and Define events/list
Declare the events capability
Your server has to advertise the top-level events capability during initialization. We suggest treating this declaration as a launch blocker rather than a detail to fill in later. In the Go SDK this is exactly the gap the open issue describes, so check where it stands before you pick a workaround.
Design each event type for two readers
We suggest designing each entry in events/list around these parts:
- A clear name that stays stable over time.
- A description that works for the user and the model alike. The user reads it while picking what to monitor; the model reads it while deciding what to do.
- An inputSchema defining the filters a subscriber can set.
- A payloadSchema describing exactly what you will send.
The example below is illustrative. Check the exact field names against OpenAI's documentation before copying it.
{
"name": "ticket.status_changed",
"description": "A support ticket in a project you can access changed status.",
"inputSchema": {
"type": "object",
"properties": {
"project_id": { "type": "string" },
"status": { "type": "string", "enum": ["open", "pending", "closed"] }
},
"required": ["project_id"]
},
"payloadSchema": {
"type": "object",
"properties": {
"ticket_id": { "type": "string" },
"new_status": { "type": "string" },
"summary": { "type": "string" }
}
}
}Keep filters narrow and meaningful to the user. A project or a ticket status works well, so people get the events they asked for and little else. Treat the payloadSchema as a public contract: version it, don't break it, and keep payloads lean.
Scope events to the authenticated user
The draft states that authorization uses the same MCP principal as your tools. In practice, list only the event types the authenticated user is allowed to see, and accept only filters that point at resources that user can reach.
ASVD Step 2 (Subscribe): Idempotent events/subscribe and events/unsubscribe
Clients retry. Networks lose responses. If a repeated subscribe request creates a second subscription, every event goes out twice and someone's automation fires twice. Design both methods so that calling them again changes nothing.
Subscribe
Store each subscription with its event type, normalized filters, callback URL, signing secret and owner. Build a deterministic key from the owner, event type, normalized filters and callback URL. When an authorized user repeats the request, they get the existing subscription back.
authorize(owner_id, event_name, filters) // always first, even on repeats
key = hash(owner_id, event_name, canonical_json(filters), callback_url)
if store.exists(key): return store.get(key)
verify_callback(callback_url) // ASVD Step 3
sub = store.create(key, secret=new_secret(), status="active")
return subAuthorization runs before the existence check on purpose. Swap them, and a user whose access was revoked could resend an old subscribe call and quietly get the subscription back.
Unsubscribe
Unsubscribe should be safe to call twice. If the subscription is already gone, return success, not an error. The caller wanted it removed, and it has been.
Persistence and authorization
Keep subscriptions in durable storage. An in-memory map loses everything on the next deploy, and deliveries stop without any visible error. Check authorization at subscribe time and again at delivery time, since access can be revoked in between.
You also need a cleanup path. OpenAI's docs say not to retry a delivery that returns 410. Our reading is that the receiver no longer wants the subscription, so mark it retired rather than letting it fail on every future event.
ASVD Step 3 (Verify): Confirm the Callback With the Signed Challenge
Before any subscription goes active, send a signed verification challenge to the callback URL and confirm the expected response. That way your server never pushes events to an endpoint that hasn't proved it is the intended receiver.
If verification fails, OpenAI's docs say to return JSON-RPC error -32015 (CallbackEndpointError) with a categorized data.reason, such as challenge_failed or timeout.
Watch for drift here. The current draft sketch says its earlier revisions used -32011 to -32015, a sub-range the specification never assigns, and that implementations of those revisions need to renumber. Put simply, the code in OpenAI's docs comes from an older sketch revision. Follow OpenAI's documentation for now, because ChatGPT is the client you are building for, and define the code once as a named constant so a future change is a one-line edit.
Give the challenge request a bounded timeout and log the reason category on every failure. When a user tells you their automation never started, that log line is the first place to look. If you already instrument agent runs, our OpenTelemetry GenAI tracing playbook is a natural home for verification and delivery telemetry.
ASVD Step 4 (Deliver): Sign and Send Events as Standard Webhooks
Deliveries follow the Standard Webhooks specification. According to Svix, X-MCP-Subscription-Id is the only MCP-specific header, and it tells the receiver which subscription, and therefore which secret, to verify the signature with.
Size and shape
OpenAI's docs say to send one event per request, with a complete request body no larger than 256 KiB (262,144 bytes). ChatGPT can group separately delivered events into one task run, based on the task's batching settings. Don't treat 256 KiB as a target. Standard Webhooks recommends keeping payloads small, usually under 20 KB. Send identifiers and a short summary, and let ChatGPT call your tools when it needs the full record.
Retries and stop conditions
OpenAI's docs say to retry transient failures with exponential backoff and bounded attempts, preserving the event ID and generating a fresh signing timestamp and signature for each attempt. Keeping the same event ID gives the receiver something to deduplicate on, and the fresh timestamp and signature keep the request from being rejected as stale.
OpenAI's docs also say not to retry deliveries that return 410 or 413. When a delivery is refused this way, we suggest reviewing your subscription state and payload design rather than sending the request again.
| Delivery outcome | What your server should do |
|---|---|
| 2xx | Mark delivered |
| Timeout or transient 5xx | Retry with exponential backoff, same event ID, new timestamp and signature, bounded attempts, per OpenAI's docs |
| 410 | Do not retry, per OpenAI's docs; retiring the subscription is a reasonable response |
| 413 | Do not retry, per OpenAI's docs; treat it as a payload design bug |
Standard Webhooks recommends a request timeout of 15 to 30 seconds. Pick your number on purpose instead of inheriting whatever your HTTP library ships with.
Use an official library
Sign with an official Standard Webhooks library, such as the Standard Webhooks JavaScript library or the official one for your stack, instead of writing your own HMAC code. A library built against the specification is the safer way to produce signatures that receivers can verify.
Test Before You Ship: A Pre-Release ASVD Checklist
Run through this list in staging, connected to a real ChatGPT subscription, before you release. Each item exercises one stage of the loop.
Retries keep the event ID, refresh the signature, and stop on 410 and 413.
- [ ] Advertise, filters: Events outside a subscription's filters never get delivered. Test with events that match some filters but not all.
- [ ] Subscribe, duplicates: Repeating a subscribe call returns the existing subscription and creates nothing new.
- [ ] Subscribe, unsubscribe: After unsubscribing, nothing else goes out, including retries already queued. A second unsubscribe call returns success.
- [ ] Subscribe, restarts: After a restart and a redeploy, subscriptions and secrets persist and deliveries resume.
- [ ] Subscribe, authorization: Revoke a user's access to a filtered resource. Deliveries for it stop, and a repeated subscribe call is refused.
- [ ] Verify, callback challenge: Run one passing challenge and one failing one. The failure returns the documented error code with a data.reason.
- [ ] Deliver, replayed events: Replay the same event ID and write down what ChatGPT does with it. Don't assume deduplication you haven't observed.
- [ ] Deliver, size limits: Payloads just under and just over 256 KiB are each handled the way you intended.
Common Mistakes When Building MCP Events Servers
- Building modes nobody calls. Polling, streaming, and gap or terminated notifications aren't part of ChatGPT's integration. Heartbeats belong in the same bucket. The draft says a server should send a heartbeat at least every 30 seconds, but that applies to push-mode streaming, which ChatGPT does not support.
- Getting retries backwards. A new event ID on each retry takes away the receiver's deduplication key. Reusing the old timestamp and signature invites a rejection for staleness.
- Retrying dead ends. OpenAI's docs say not to retry deliveries that return 410 or 413. The same body will bounce again.
- Shipping whole records. Full documents push payloads toward 256 KiB when Standard Webhooks suggests staying under 20 KB.
- Holding subscriptions in memory. They disappear on restart, and nothing warns you.
- Trusting payload text. When an event carries user-generated text, a customer message for example, that text can contain instructions aimed at the model. Treat payloads as untrusted input, the same way you treat tool results, and strip or clearly delimit that text.
Caveats: Spec Drift and Operational Limits
The spec is a draft. OpenAI's error code and the current sketch's renumbering already disagree, and the working group is still adjusting its scope. Build against OpenAI's documentation and re-read it on a regular schedule.
Your custom code is temporary. SDK support is uneven. Plan to replace the handlers once official support ships.
Unattended automation needs guardrails. Before an event-triggered task touches production systems, decide which actions it may take without a person signing off. Add audit logs and rate limits, and give each subscription its own kill switch. For more on narrowing what a triggered task can reach, see our note on how to contain a compromised agent's reach. If triggered work grows into multi-step jobs, our durable AI agents runtime selection checklist can help you decide where that state should live.
Confirm secondhand details. The launch date and protocol version mentioned above come partly from third-party guides. Check them against OpenAI's documentation.
Key Takeaways
- 1Build in the Advertise-Subscribe-Verify-Deliver (ASVD) loop: advertise events, subscribe idempotently, verify callbacks, then deliver signed events.
- 2ChatGPT supports only webhook delivery and callback verification from the draft MCP Events spec, so skip polling, streaming, and gap and terminated notifications.
- 3Run authorization before returning an existing subscription, make subscribe and unsubscribe idempotent, and keep subscriptions in durable storage.
- 4Verify every callback URL with a signed challenge before activation. Keep the failure error code in one constant, since the draft has renumbered it.
- 5OpenAI's docs cap each single-event request body at 256 KiB, yet Standard Webhooks recommends payloads under 20 KB.
- 6OpenAI's docs say not to retry a 410 or 413, and to retry transient failures with the same event ID and a fresh signature.
- 7Official Python and TypeScript SDKs lacked MCP Events support at launch and the Go SDK can't yet advertise the capability, so isolate custom handlers.
Conclusion
Svix calls ChatGPT the first major client to support MCP Events. For teams that already run an MCP server or a SaaS API, that makes this a reasonable time to let changes in your own systems start work in ChatGPT, instead of waiting on typed prompts. Most of the build is webhook engineering you have probably done before, arranged as the ASVD loop.
The tricky parts are the ones still in motion. The draft's error codes have already shifted once, and SDK support is patchy. There is also a security question that deserves real thought: what should an unattended task be allowed to do with payload text it didn't write?
Our suggested path is modest. Ship one narrow, well-filtered event type. Put it through the pre-release checklist, then watch it in production with logging and a kill switch ready. Keep the custom protocol code thin and separate from business logic, so a later spec change costs a small edit rather than a rewrite. Add more event types once the first one has behaved reliably for a while.
Frequently Asked Questions
What are ChatGPT MCP Events?
MCP Events let ChatGPT subscribe to updates from your MCP server, such as new messages, content updates or status changes. OpenAI's documentation says ChatGPT supports webhook delivery and callback verification from the draft MCP Events specification. Users choose what to monitor and what ChatGPT should do when an update arrives, so a connector can kick off an automation without waiting for a prompt.
Does ChatGPT support polling or streaming for MCP Events, or only webhooks?
Only webhooks. OpenAI's documentation says ChatGPT supports webhook delivery and callback verification from the draft MCP Events specification. Polling, streaming, and the draft's gap and terminated control notifications are not supported by this integration, so you can leave those code paths out.
What is the maximum event payload size ChatGPT accepts, and how big should payloads really be?
OpenAI's docs say ChatGPT accepts one event per request with a complete body no larger than 256 KiB (262,144 bytes). Treat that as a ceiling. The Standard Webhooks specification recommends keeping payloads small, usually under 20 KB. In practice, send identifiers and short summaries and let ChatGPT fetch full records through your tools.
How should retries work, and which HTTP responses mean I should stop retrying?
OpenAI's docs say not to retry deliveries that return 410 or 413. We suggest treating either response as a prompt to change something on your side, such as the subscription's status or your payload design, rather than trying again. OpenAI's docs also say to retry transient failures with exponential backoff and bounded attempts, preserving the event ID and generating a fresh signing timestamp and signature for each attempt.
Which error should my server return when callback verification fails, and why might the code change?
OpenAI's docs say to return JSON-RPC error -32015 (CallbackEndpointError) with a categorized data.reason such as challenge_failed or timeout. The current draft sketch, however, says earlier revisions used -32011 to -32015 and that implementations need to renumber. Follow OpenAI's docs for ChatGPT, keep the code in a single constant, and re-check the docs regularly.
Can I use the official Python, TypeScript or Go MCP SDKs to build MCP Events today?
Partly. A community developer reported that the official Python SDK (mcp 2.2.0) and the TypeScript SDK did not support MCP Events at launch. In the Go SDK you can implement events/list, events/subscribe and events/unsubscribe as custom methods, but advertising the top-level events capability is still missing. Check current releases, and keep any custom handlers isolated so you can replace them later.
Sources
- https://developers.openai.com/plugins/build/mcp-events
- https://github.com/modelcontextprotocol/experimental-ext-triggers-events/blob/main/docs/design-sketch-proposal.md
- https://modelcontextprotocol.io/community/working-groups/tasks-events
- https://github.com/standard-webhooks/standard-webhooks/blob/main/spec/standard-webhooks.md
- https://github.com/standard-webhooks/standard-webhooks/tree/main/libraries/javascript
- https://www.svix.com/blog/mcp-events/
- https://dev.to/hassann/mcp-events-explained-build-and-test-a-webhook-emitting-mcp-server-chatgpt-can-subscribe-to-4od8
- https://community.openai.com/t/mcp-events-for-the-official-python-mcp-sdk-tested-end-to-end-with-chatgpt/1402999
- https://github.com/modelcontextprotocol/go-sdk/issues/1325
- https://community.openai.com/t/devday-2026-announcements-and-developer-resources/1402006
Written by
Hamza DiazHamza Diaz is the founder of Optijara, where he builds practical AI agents, automation systems, and Copilot workflows for service businesses. He writes about AI operations, agent strategy, and real-world implementation for teams that want usable systems instead of hype.
