Use webhooks to respond to session state changes without keeping an event stream open. A webhook handler can start or reconnect sandbox compute, update your application, or trigger a workflow.
Supported events
| Event | When it fires |
|---|---|
agent.session.created | A session is created. |
agent.session.action_required | The session needs a function result, initial environment connection, or reconnection. |
agent.session.in_progress | The session starts processing a turn. |
agent.session.idle | The session is idle and ready for more input. |
agent.session.failed | The session enters a failed state. |
An agent.session.action_required event includes the session ID and a
required_action.type of function_call or environment_connection.
{
"type": "agent.session.action_required",
"data": {
"id": "sess_abc123",
"required_action": { "type": "function_call" }
}
}
Retrieve the session and inspect required_actions for call IDs, arguments, or
environment IDs. The webhook does not include those details.
Set up a webhook
Follow the shared webhook setup guide to create an endpoint and select Agents API events. Store the endpoint’s signing secret for signature verification.
Receive events
OpenAI sends a signed HTTP POST request whenever a subscribed event occurs:
{
"id": "evt_123",
"object": "event",
"created_at": 1750287018,
"type": "agent.session.created",
"data": {
"id": "sess_abc123",
"environment_id": "ccarenv_abc123",
"environment_type": "self_hosted",
"connect": {
"remote_url": "https://api.openai.com/v1/agents/api"
}
}
}
Retrieve the session’s current state before provisioning a sandbox. See Sandbox lifecycle.
Start the executor
For self-hosted sessions, agent.session.created includes the environment ID and connection URL needed to start an executor. Set ENVIRONMENT_ID to data.environment_id and REMOTE_URL to data.connect.remote_url. This is the same URL returned as environment.remote_url on the session. Save both values and reuse them on reconnect:
CODEX_API_KEY="$OPENAI_ENVIRONMENT_KEY" \
codex exec-server \
--remote "$REMOTE_URL" \
--environment-id "$ENVIRONMENT_ID"
Use an environment key as CODEX_API_KEY. Keep your application API key outside the environment.
Verify and process events
Set OPENAI_API_KEY and OPENAI_WEBHOOK_SECRET. For Python, install fastapi, uvicorn, and openai. For JavaScript, install express and openai.
The handlers verify signatures and listen on port 8000. Set PORT to change the port. In production, queue slower work.
import json
import os
import uvicorn
from fastapi import FastAPI, Request, Response
from openai import AsyncOpenAI, InvalidWebhookSignatureError
app = FastAPI()
webhooks = AsyncOpenAI(webhook_secret=os.environ["OPENAI_WEBHOOK_SECRET"])
@app.post("/webhooks/openai")
async def handle_webhook(request: Request):
payload = await request.body()
try:
webhooks.webhooks.verify_signature(payload=payload, headers=request.headers)
except (InvalidWebhookSignatureError, ValueError):
return Response("Invalid signature", status_code=400)
event = json.loads(payload)
if event["type"] == "agent.session.idle":
session_id = event["data"]["id"]
session = await webhooks.beta.agents.sessions.retrieve(session_id, timeout=10)
print("session idle event:", session.id)
else:
print("session event:", event["type"], event["data"]["id"])
return Response(status_code=200)
if __name__ == "__main__":
uvicorn.run(app, port=int(os.environ.get("PORT", "8000")))Environment connection events
When initial or follow-up input needs a disconnected self-hosted executor, the API adds an environment_connection required action. It emits agent.session.action_required before waiting for the connection.
Retrieve the session and confirm that required_actions still requests a connection. Start the executor with session.environment.id and session.environment.remote_url. This webhook does not include connect.remote_url. If the executor connects before the wait expires, the API clears the required action and resumes the submission without client resubmission.
The API waits up to five minutes for the connection. A follow-up input request can remain open during this wait. Configure client and proxy timeouts accordingly. agent.session.in_progress confirms execution has started, not that the API is waiting for a connection.
If the wait expires, the submission fails. Initial input can fail asynchronously and leave the session in failed. The connection wait does not provide a durable input queue. A process crash or client disconnect may require retries.
Session and turn outcomes
agent.session.idle means the session is ready for more input, not that its last turn succeeded. Inspect that turn’s status or observe agent.session.turn.completed, agent.session.turn.failed, or agent.session.turn.cancelled on the session stream. A completed turn can still contain failed tool calls. Check tool results and the agent’s final response.
agent.session.failed reports a failed session, not every failed turn. Session deletion has no corresponding webhook and does not stop provider compute.