ChatGPT decision events
Wake a ChatGPT automation when one Pushary question gets its answer. Subscribe to decision.answered over MCP Events and verify each signed webhook.
MCP Events let a ChatGPT automation you authorized react when one Pushary question gets its answer. When that question is answered, Pushary sends a signed webhook to ChatGPT. The event only wakes the automation. It does not resume other tasks and it never approves an action by itself, because the answer can be a denial.
ChatGPT still chooses when to ask you a question. An event only tells it that an answer arrived.
Deployment status
Implemented behind PUSHARY_MCP_EVENTS_ENABLED=true. Apply migration 0098_mcp_event_subscriptions first. Keep the flag off until the live ChatGPT callback check and the authorized automation flow pass. Availability also depends on the user's access to ChatGPT Events and Automations.
Subscribe to one question
Connect ChatGPT
Use the signed-in ChatGPT connector. Events use the same MCP endpoint and the same sign-in.
Ask a question and keep its id
Create an owner question with ask_user. Keep the correlationId it returns.
Authorize the follow-up in ChatGPT
In the ChatGPT automation setup, authorize the follow-up and select decision.answered with that correlationId. ChatGPT supplies its webhook URL and signing secret. The model must never invent either.
ChatGPT then sends an events/subscribe request like this:
{
"jsonrpc": "2.0",
"id": 1,
"method": "events/subscribe",
"params": {
"name": "decision.answered",
"arguments": { "correlationId": "saved-question-id" },
"delivery": {
"mode": "webhook",
"url": "https://your-authorized-receiver.example/events",
"secret": "whsec_BASE64_ENCODED_24_TO_64_BYTE_KEY"
},
"cursor": null,
"ttlMs": 3600000
}
}Pushary rejects a request that breaks these rules:
correlationIdis 1 to 256 characters.urlishttps, on port 443 or no port, with no username, password or#fragment, and at most 2,048 characters.secretiswhsec_followed by the base64 of a 24 to 64 byte key.
The same endpoint also answers events/list and events/unsubscribe.
Verify the callback
Before Pushary saves a subscription, it sends a signed verification POST with the body {"type":"verification","challenge":"..."}. Your receiver must answer with a 2xx JSON response that echoes {"challenge":"the-received-challenge"}.
Every request carries Standard Webhooks headers: webhook-id, webhook-timestamp and webhook-signature, plus X-MCP-Subscription-Id. The signature is v1, and a base64 HMAC-SHA256 of webhook-id.webhook-timestamp.body, keyed with the base64-decoded secret. Check it against the raw body, check the subscription id, and allow only a short timestamp drift. Drop event ids you have already seen.
What an event contains
The event body has eventId, name, timestamp, cursor: null and data: { correlationId, status: "answered" }. It does not carry the answer.
Fetch the saved answer with wait_for_answer. An answered question can be a denial. Check the exact action you asked about before you run anything.
Who can subscribe
Each subscription belongs to one signed-in principal, workspace, question and callback URL. Only owner questions work. Partner customer decisions and bound keys are excluded.
Renew and change the secret
A subscription lives at most one day. Renew it before the refreshBefore time Pushary returns. Repeating a subscription refreshes its expiry and does not redeliver an event you already acknowledged.
A refresh can replace the signing secret, once the callback passes verification with the new key. For five minutes, deliveries carry signatures from both keys, then only the new key. A passed verification is cached for five minutes, and changing the secret always needs a new verification.
Retries and failures
A retry cron reads the saved decision and claims a delivery lease for up to five events per run. A subscription generation number stops an old delivery from changing a replaced or re-keyed subscription. Expired subscription records are deleted.
- A 2xx response acknowledges the event.
- Other failures retry with exponential backoff, starting at 30 seconds and capped at one hour, up to eight attempts.
- HTTP 410 or 413 stops delivery.
- Expired, unsubscribed, revoked API key and removed workspace member subscriptions cannot start another delivery.
- Disconnecting an OAuth connection deletes its subscriptions in the database, even while event delivery is turned off. Reconnecting needs a new subscription.
- A request already in flight cannot be recalled.
Pushary does not keep checking whether the upstream token was revoked. The one-day limit on each subscription bounds that gap.
What does not send an event
There is no history replay, no stream and no polling delivery mode. A question that was already answered, and is still kept, can send its one event after a new subscription. A question that expires or is cancelled does not send decision.answered.
If delivery runs out of retries, poll the question itself with wait_for_answer. That stays the source of truth.
Protocol support
When the flag is on, the public server/discover method advertises protocol 2026-07-28. MCP 2 tool calls use the same signed-in tool list, with complete-result envelopes and private, uncached list responses. Multi-round-trip inputs, tasks and MCP 2 resource methods are not supported. Older MCP clients keep their existing transport.
A compatibility bridge keeps the current SDK 1 and SSE integrations working. A later move to native mcp-handler 2 will replace this bridge and needs registration, auth context and clients migrated. You do not need that move to try these event and tool methods.
See OpenAI's MCP Events specification.
Next steps
Connect ChatGPT
Install the Pushary plugin or add a custom app, then sign in.
How questions and answers work
Question types, timeouts, and what happens when nobody answers.