The OpenAI Agents API hosts the Codex agent harness (OpenAI's coding-agent runtime), the inference loop, and session state. Vercel hosts the application and provides the infrastructure that connects each agent session to an execution environment.
The sample Next.js app uses Vercel Functions to create sessions, submit input, stream events, and receive signed webhooks. Vercel Queues handles lifecycle events, while Vercel Sandbox provides a persistent, isolated execution environment for each session. OpenAI runs the agent, and Vercel runs the application and the infrastructure that manages its Sandbox lifecycle.
Clone the sample app, customize it, and read on to see how it all works.
OpenAI Agents API on Vercel
OpenAI agent sessions connected to isolated Vercel Sandboxes through signed webhooks and a Vercel Queue.
Copy link to headingWhat you will build
The finished app lets a user create an OpenAI agent session, submit a task, and stream its events. When input is submitted and the executor is disconnected, OpenAI sends a connection-required webhook. The handler starts or reconnects the Sandbox, then the waiting turn proceeds.
There are two independent paths:
- The agent path carries user input and streamed output between your app and OpenAI.
- The infrastructure path turns an OpenAI lifecycle webhook into a running Sandbox.
During a run, the pieces connect in this order:
- The app creates a session with a
self_hostedenvironment and sends input. - OpenAI sends a signed webhook when the session needs an executor.
- The webhook places the session ID on a Queue and responds immediately.
- A private consumer reads the current session state and creates or resumes its Sandbox.
codex exec-serverconnects outbound from the Sandbox to OpenAI.- OpenAI runs the agent and streams session events back to the app.
The OpenAI environment ID identifies the executor connection for the life of a session. It is different from the Vercel Sandbox ID. The app uses the OpenAI session ID to deterministically name the corresponding Sandbox.
Copy link to headingPrerequisites
Before you begin, you need:
- An OpenAI project with access to the Agents API
- A Vercel account with Sandbox and Queues access
- Node.js 24 or later
- The Vercel CLI
- Permission to create an application key and an environment key in the same OpenAI organization and project
Copy link to headingCreate the OpenAI credentials
Open the OpenAI API keys page for the project with Agents API access and create an application key. Grant it api.agents.read, api.agents.write, and api.responses.write. The Vercel app uses this key to create and manage agents and sessions.
Create a second environment key for the executor from the Agents tab in the OpenAI platform dashboard. Both keys must belong to the same organization, project, and user or service account. The environment key is the only OpenAI credential passed into the Sandbox. Keep the broader application key and webhook signing secret in Vercel Functions.
Each key has a different scope and location:
| Application key | Environment key | |
|---|---|---|
| Permissions | api.agents.read, api.agents.write, api.webhooks.write and api.responses.write | Created from the Agents tab for executor connections |
| Where it lives | Vercel Functions environment variables | Passed into the Sandbox as CODEX_API_KEY |
| What it does | Creates agents and manages sessions | Connects codex exec-server to the session |
Copy link to headingSet up the project
Clone the sample Next.js app and install its dependencies:
The project uses the official openai@7.15.0 TypeScript SDK, @vercel/queue, and @vercel/sandbox for the server-side integration. The interface uses streamdown to render streamed Markdown as the agent responds.
Create the reusable agent with the sample's SDK script:
The command prints an ID beginning with agent_. Save it as OPENAI_AGENT_ID. The agent ID is not a model ID or API key; it identifies the reusable agent configuration that each new session loads.
Link the app to a Vercel project:
Deployed Functions authenticate to Sandbox and Queues automatically through OIDC. You will pull a local development token after configuring and deploying the project.
Add the application settings to .env.local:
Each value comes from a different part of setup:
| Variable | Where it comes from | What it does |
|---|---|---|
OPENAI_API_KEY | The application key created in the OpenAI project | Creates agents and manages sessions from Vercel Functions |
OPENAI_AGENT_ID | The agent_... value returned by the agent creation request | Selects the reusable agent configuration for each session |
OPENAI_EXECUTOR_API_KEY | The environment key created from the OpenAI project's Agents tab | Lets codex exec-server connect the Sandbox to the session |
OPENAI_WEBHOOK_SECRET | The signing secret returned after webhook registration | Verifies that lifecycle requests came from OpenAI |
APP_PASSWORD | A strong value you choose | Protects the sample UI and session routes |
APP_PASSWORD protects the sample UI and session routes with a signed, HTTP-only cookie. Choose a strong value. Replace this shared password with your identity provider and authorization policy in a production application.
You cannot add the real webhook secret yet. OpenAI creates it after you register the deployed webhook URL later in this guide.
Copy link to headingReview the project structure
The walkthrough follows these files in dependency order:
The following snippets focus on the integration boundaries. The cloned project contains the complete implementations, including shared types, error handling, tests, and the demo interface.
Copy link to headingCreate the OpenAI session client
Create lib/openai.ts and initialize the official OpenAI TypeScript SDK:
Create a self-hosted session through client.beta.agents.sessions:
Expose that helper from app/api/sessions/route.ts. The route returns the new session to the browser.
At this point OpenAI knows that the session requires a self-hosted executor, but there is no Sandbox yet.
Copy link to headingReceive OpenAI lifecycle webhooks
Create app/api/webhook/route.ts. This is the only public infrastructure endpoint OpenAI needs to call.
Verify the signature against the unmodified request body before parsing it:
Handle two event types:
agent.session.action_requiredwakes the infrastructure when OpenAI needs anenvironment_connection.agent.session.failedremoves compute for a failed session.
Publish the session ID to the Queue:
Using the OpenAI event ID as the Queue idempotency key deduplicates repeated webhook deliveries. Provisioning runs after the webhook responds, so OpenAI does not need to wait for a Sandbox to boot.
Copy link to headingReconcile the current OpenAI session
Queue delivery is at least once. A message may be retried, duplicated, or arrive after another message already connected the executor. The consumer must retrieve the latest OpenAI session instead of treating the webhook payload as a command.
Add retrieval to lib/openai.ts:
Then create app/api/queues/provision/route.ts and inspect the current state:
The consumer uses the current environment ID and remote URL rather than values copied from the webhook. This read-before-write pattern makes each Queue delivery a stateless reconciliation.
Copy link to headingCreate or reconnect the Sandbox
Create lib/sandbox.ts. Use the OpenAI session ID as the deterministic Sandbox name so retries resolve to one microVM:
This uses the Vercel-managed Node.js 24 image. persistent: true preserves the session's /workspace filesystem if the Sandbox stops and later resumes.
Allow these outbound hosts:
Each host serves one purpose:
| Host | Why it is needed |
|---|---|
api.openai.com | Registers the executor with the Agents API |
codex-cloud-environments.chatgpt.com | Carries file and shell operations over an outbound WebSocket |
registry.npmjs.org | Installs the Codex CLI |
The npm registry is only needed during CLI installation. For production, bake the CLI into a custom image and remove the registry from the allowlist.
Install the executor once under an OS lock:
The setup creates /workspace with ownership that lets the executor read and write files without elevated permissions. The lock prevents concurrent Queue consumers from changing the directory or installing the CLI twice.
Copy link to headingConnect the executor to OpenAI
Start codex exec-server with the environment ID and remote URL from the current OpenAI session:
Startup uses a second non-blocking flock. This prevents concurrent deliveries from starting competing executor processes in the same Sandbox.
The executor registers over api.openai.com, then opens an outbound WebSocket to codex-cloud-environments.chatgpt.com. OpenAI sends file and shell operations over that connection and the executor returns their results. The Sandbox does not need an inbound port.
Copy link to headingConfigure the Queue consumer
Wrap the reconciliation function with the Queue callback handler:
Map the sandbox-wakeup topic to the Function in vercel.json:
handleCallback() processes an incoming Queue delivery, but it does not register the route as a consumer. The queue/v2beta trigger creates that deploy-time subscription and makes the Function private. Without it, messages can enter the Queue but this Function will not receive them.
The Function duration remains beside the consumer implementation rather than in vercel.json. The producer uses iad1, and the callback derives the delivery region from the Queue request. You do not need to pin every Function in the project with a top-level regions setting.
Copy link to headingSend input and stream one turn
Create app/api/sessions/[id]/input/route.ts. The SDK's session stream helper subscribes before sending input and follows the resulting turn until it ends:
The helper prevents a short turn from finishing before the app starts listening and stops after the selected turn ends and the reusable OpenAI session becomes idle. createBrowserEventStream() serializes the SDK events for the browser. The sample implementation contains the complete parser and termination logic.
The Sandbox does not stream output directly to the browser. It returns command results to OpenAI, OpenAI continues the agent loop, and the OpenAI session emits the output events consumed here.
Copy link to headingAdd the demo interface
The remaining presentation layer is a client component that:
- Accepts a task.
- Calls
POST /api/sessionsonce. - Calls
POST /api/sessions/:id/inputfor the first task and follow-ups. Records each returned SSE block in the session event list, but appends only
agent.session.turn.output_text.deltaevents to the assistant message so completed content is not rendered a second time.Groups text deltas by content part and preserves paragraph boundaries between
parts.
- Renders assistant output as streaming Markdown with
streamdown. - Calls
DELETE /api/sessions/:idto remove both the session and Sandbox.
Copy components/demo.tsx and app/page.tsx from the companion app. These files contain only the interface; all credentials and infrastructure operations remain in server routes.
Copy link to headingRun locally
Pull the project environment variables before starting the app:
This also writes a short-lived OIDC token to .env.local. The token gives the Sandbox SDK your project's identity during local development. Pull the environment again when that token expires.
Run the app to verify the interface:
Session creation and streaming can run locally. Use a deployed webhook for the complete managed Queue delivery and Sandbox lifecycle.
| Command | What it does |
|---|---|
pnpm dev | Starts the local development server |
pnpm test | Runs the test suite |
pnpm build | Verifies the production build |
pnpm probe:sandbox | Creates a Sandbox, installs the Codex CLI, prints its version, and stops it |
Copy link to headingDeploy the app
Add the variables available before webhook registration, then deploy:
Vercel supplies Sandbox and Queue authentication through OIDC. Do not configure a separate Vercel access token in the deployed app.
Copy link to headingRegister the OpenAI webhook
Open the webhook settings for the same OpenAI project used to create the agent. The application key must include the api.webhooks.write permission. Register https://YOUR_PROJECT.vercel.app/api/webhook and subscribe to:
agent.session.action_requiredagent.session.failed
Copy the resulting signing secret into the Vercel project and redeploy:
Until the real signing secret is configured, the sample endpoint responds with 503. OpenAI must be able to reach the webhook without a Vercel login. The OpenAI signature, rather than a bearer token in the URL, authenticates each delivery.
If Vercel Deployment Protection is enabled, create an automation bypass secret and register the webhook URL with ?x-vercel-protection-bypass=YOUR_BYPASS_SECRET. This lets OpenAI reach only that URL without disabling protection for the deployment.
Copy link to headingTest the complete flow
Verify the project and managed image first:
The probe creates a real Sandbox, installs the Codex CLI, prints its version, and stops it.
Open the production app and ask the agent to create a file in /workspace, run it, and report the result. You should see:
- An environment-pending event while OpenAI waits for the executor.
- An environment-connected event after the Queue consumer starts the Sandbox.
- Turn and output events as the agent works.
- An idle event when the turn completes.
Send a follow-up in the same session and ask the agent to read the file. This confirms that the session still owns the same persistent workspace. Use Delete session to remove the OpenAI session and its Sandbox.
Copy link to headingWhy use a Queue instead of Workflow?
The Queue does not run the agent or store user prompts. It only makes delivery of the infrastructure wake-up signal durable.
Sandbox provisioning can take longer than a webhook should stay open, and either OpenAI or Vercel can retry a request. A Queue absorbs those bursts, retries transient failures, and invokes a private consumer.
Four safeguards make at-least-once delivery safe:
- The OpenAI webhook event ID deduplicates Queue messages.
- The consumer reads the latest OpenAI session before acting.
- The session ID deterministically identifies one Sandbox.
- OS locks allow only one setup and executor process inside that Sandbox.
Vercel Workflow is useful when your app owns a long-running sequence of steps, waits, or human approvals. Here, OpenAI already owns the long-running session state. Adding a Workflow would create a second lifecycle without removing the need to reconcile OpenAI's state.
Copy link to headingHow this differs from the OpenAI Agents SDK integration
The existing guide to building an agent with the OpenAI Agents SDK and Vercel Sandbox runs the agent loop in your application. Your code calls the model, decides when to use tools, and manages the loop.
In this integration, OpenAI hosts that loop. Your application creates sessions, sends user input, and displays events. Its infrastructure responsibility is narrower: connect an isolated Sandbox whenever OpenAI says the session needs an executor.
| Agents SDK guide | This guide | |
|---|---|---|
| Agent loop | Your application | OpenAI Agents API |
| Session state | Your application | OpenAI |
| Command execution | Vercel Sandbox | Vercel Sandbox |
| Sandbox lifecycle | Your application | Signed webhook and Queue consumer |
Copy link to headingCustomize the sample app
| To change | Edit | Notes |
|---|---|---|
| Agent model or behavior | The saved OpenAI agent | Run pnpm create:agent after changing scripts/create-agent.ts, then update OPENAI_AGENT_ID |
| Authentication | lib/auth.ts and APP_PASSWORD | Replace the shared password with your identity provider and authorization policy |
| Sandbox image | image in lib/sandbox.ts | Bake the Codex CLI into a custom image and remove registry.npmjs.org from the network policy |
| Sandbox lifetime | SANDBOX_TIMEOUT_MS in lib/config.ts | Set it above the longest expected turn |
| Queue region | The webhook producer and consumer QueueClient | Choose the region where your Functions run to reduce delivery latency |
Copy link to headingProduction considerations
The sample cleans up when the app deletes a session or OpenAI reports a failed session. OpenAI does not send a deletion webhook, so deletion performed elsewhere does not automatically remove the Sandbox. Production apps should periodically compare retained Sandboxes with their session records and remove orphaned resources and snapshots.
For observability, record the webhook event ID, session ID, environment ID, Sandbox name, provisioning duration, and cleanup reason. Never log OpenAI API keys or webhook signatures.
Set the Sandbox timeout above the longest expected turn. Persistent Sandboxes preserve files across normal stops, but processes such as codex exec-server must be restarted when a Sandbox resumes. Rebuild custom images regularly to keep the Codex CLI and system packages current.
Copy link to headingTroubleshooting
Each item below lists a symptom, its cause, and the fix.
Copy link to headingThe webhook responds with 503
Symptom: OpenAI reports failed webhook deliveries, and requests to /api/webhook return 503.
Cause: OPENAI_WEBHOOK_SECRET isn't set, so the route can't verify signatures and refuses every delivery.
Fix: Register the webhook in the OpenAI project, add the resulting signing secret with vercel env add OPENAI_WEBHOOK_SECRET, and redeploy.
Copy link to headingSessions stall on environment-pending
Symptom: The app shows an environment-pending event, but no environment-connected event follows.
Cause: The webhook isn't reaching the app, or messages enter the Queue with no consumer. Deployment Protection can block OpenAI's deliveries, and a missing queue/v2beta trigger in vercel.json leaves the provision Function unsubscribed.
Fix: Confirm the webhook is registered against the production URL, append the protection bypass secret if Deployment Protection is enabled, and check that vercel.json maps the sandbox-wakeup topic to app/api/queues/provision/route.ts.
Copy link to headingThe executor never connects
Symptom: The Sandbox starts, but the pending environment_connection action never clears.
Cause: The Sandbox network policy blocks a host the executor needs, or the Codex CLI install failed.
Fix: Allow api.openai.com, codex-cloud-environments.chatgpt.com, and registry.npmjs.org in the network policy, then run pnpm probe:sandbox to confirm the image can install and run the CLI.
Copy link to headingLocal sessions never get an executor
Symptom: Streaming works with pnpm dev, but locally created sessions wait on the environment forever.
Cause: OpenAI delivers webhooks to the registered production URL, not to your local server, so the local app never receives the wake-up signal.
Fix: This is expected. Test the full lifecycle against the deployed app, and use local development for the session and streaming routes.
Copy link to headingNext steps
- Explore the source code for the complete implementation.
- Learn how Vercel Sandbox isolates untrusted code, and which runtimes and images are available.
- Read the Vercel Queues documentation for topics, triggers, and more.
- Compare with Vercel Workflows for flows your application owns end-to-end.
- See the companion guide to building an agent with the OpenAI Agents SDK and Vercel Sandbox for the application-hosted agent loop.