Skip to content
OTFotf
All posts

OpenAI Agents on Vercel: own the Queue and Sandbox hosting seam

D
DaveAuthor
8 min read
OpenAI Agents on Vercel: own the Queue and Sandbox hosting seam

On September 10, 2026, Vercel published a changelog for building with the OpenAI Agents API on Vercel. The split is blunt: OpenAI manages the agent loop and session state; Vercel hosts the application; each session connects to Vercel Sandbox for code and file work. Signed OpenAI webhooks plus Vercel Queues create or reconnect that Sandbox. You get an isolated environment per session, a persistent workspace across follow-ups, and scale-to-zero without an always-on worker.

This post is the Vercel hosting seam. It is not the harness ownership post, which covers what you still own when OpenAI hosts the Codex-style Agents API harness itself. Here the ICP question is narrower: what must live in your product repo this week so webhook verification, Queue consumption, Sandbox lifecycle, secrets placement, and mid-session Sandbox death are product behavior — not a changelog paraphrase.

Signed webhook becomes a Queue ticket then Sandbox create or reconnect

Two paths, one product repo

Vercel’s step-by-step guide draws 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 (verified against the public guide):

  1. The app creates a session with a self_hosted environment and sends input.
  2. OpenAI sends a signed webhook when the session needs an executor.
  3. The webhook places the session ID on a Queue and responds immediately.
  4. A private consumer reads the current session state and creates or resumes its Sandbox.
  5. codex exec-server connects outbound from the Sandbox to OpenAI.
  6. 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 sample names the Sandbox deterministically from the OpenAI session ID (agents-${sessionId}). That naming rule belongs in your repo: retries must resolve to one microVM, not a flock of orphans.

What builders must put in the product repo this week

1. Webhook verification against the raw body

OpenAI delivers signed HTTP POSTs. Your public webhook route must verify the signature against the unmodified request body before JSON parsing. Invalid signatures return 400. Until OPENAI_WEBHOOK_SECRET is installed, the sample refuses deliveries with 503 — fail-closed on purpose.

Subscribe at least to:

  • agent.session.action_required — wake infrastructure when OpenAI needs an environment_connection
  • agent.session.failed — remove compute for a failed session

For environment wake-ups, queue only when required_action.type is environment_connection — a function_call is a different path. Use the OpenAI event ID as the Queue idempotency key so retries do not double-provision.

2. A Queue consumer that reconciles, then creates or reconnects

Provisioning can take longer than a webhook should stay open. Either side can retry. The Queue absorbs bursts and invokes a private consumer. On Vercel, handleCallback() alone does not subscribe the Function: you need the queue/v2beta experimental trigger in vercel.json mapped to your topic (the sample uses sandbox-wakeup). Without that trigger, messages enter the Queue and nobody consumes them — a silent stall that looks like “environment pending forever.”

The consumer must read the latest OpenAI session before acting. Queue delivery is at-least-once. Ignore deleted sessions and resolved actions. Start or reconnect using session.environment.id and session.environment.remote_url from retrieve — the connection webhook does not include connect.remote_url. OpenAI waits up to five minutes for the executor; configure client and proxy timeouts. If the wait expires, the submission can fail and leave the session failed.

3. Sandbox create/reconnect with persistence and locks

Use Sandbox.getOrCreate with a deterministic name, a managed image (the sample uses vercel/sandbox/node:24), persistent: true, and a timeout above your longest expected turn. Persistence preserves /workspace across stops; it does not keep codex exec-server running after a resume — restart the executor on reconnect.

Allow api.openai.com, codex-cloud-environments.chatgpt.com, and (unless the CLI is baked into a custom image) registry.npmjs.org. OS-level flock around setup and executor start prevents concurrent consumers from double-installing the CLI or starting competing processes. The Sandbox does not stream assistant output to the browser — it returns command results to OpenAI; your app streams OpenAI session events.

4. Where secrets live

The guide’s credential split is a product requirement:

SecretWhere it livesPurpose
Application keyVercel Functions envCreate agents and manage sessions
Environment / executor keyPassed into the Sandbox only (CODEX_API_KEY)Lets codex exec-server connect
Webhook signing secretFunctions envVerify OpenAI deliveries
Sandbox / Queues authOIDC on Vercel (automatic in deployed Functions)Do not stash a long-lived Vercel access token “just in case”

Keep the application key and webhook secret out of the Sandbox. Keep the executor key out of the browser. If Deployment Protection is on, register the webhook with the automation bypass query param so OpenAI can reach that one URL without disabling protection for the whole deployment.

5. Failure modes when the Sandbox dies mid-session

Plan for these in the product repo:

  • Webhook never arrives locally — OpenAI posts to the registered production URL. Local dev can exercise session create and streaming; full lifecycle needs the deployed webhook.
  • 503 on webhook — missing signing secret. Fix: register → env add → redeploy.
  • Stuck on environment-pending — webhook blocked (Deployment Protection), or Queue topic with no queue/v2beta consumer.
  • Sandbox up, executor never connects — network allowlist or CLI install failure. Probe before blaming OpenAI.
  • Sandbox stopped / timed out mid-turn — persistent filesystem may remain; executor process does not. Reconnect on the next environment_connection.
  • Session failed — consumer should delete the Sandbox. OpenAI does not send a deletion webhook when a session is deleted elsewhere, so periodically compare retained Sandboxes with session records and remove orphans.
  • At-least-once duplicates — idempotency key + session retrieve + deterministic Sandbox name + OS locks. Implement all four.

Log webhook event ID, session ID, environment ID, Sandbox name, provisioning duration, and cleanup reason. Never log API keys or signatures.

11 production screens. Login, database, payments — all wired.

The SaaS Dashboard Kit ships everything already connected. Nothing to set up. Live demo at saas.otf-kit.dev.

See the live demo

Ownership of files vs OpenAI session state

Hold these domains apart:

  • OpenAI session state — turns, compaction, agent loop, required actions, streamed events.
  • Sandbox filesystem/workspace files in the microVM. With persistent: true, files survive normal stop/resume for that named Sandbox; they are not “in the session JSON.” Artifacts that must outlive the Sandbox belong in your object store or git-backed storage.

A follow-up that reads a file created earlier tests infrastructure reattach of the same workspace — not OpenAI storing file bytes in session state. That is also why the guide prefers Queues over Vercel Workflow for the wake-up: OpenAI already owns the long-running session; a second workflow clock does not remove reconciliation.

How this sits beside the harness post

ConcernWhere it livesOTF post
Managed agent loop, compaction, subagentsOpenAI Agents APIHarness ownership
Webhook → Queue → Sandbox create/reconnectYour Vercel app + Queues + SandboxThis post
Tool permission boundaries for production writesYour MCP / tool policySafe tool permissions
Repo conventions agents can seeYour product repoProduction repo conventions

If you are choosing whether to call the Agents API at all, start with the harness post. If you already chose Agents API on Vercel with self_hosted + Sandbox, stay here until the infrastructure path is boring.

Patterns that keep product code in a repo you control — including https://otf-kit.dev and https://github.com/otf-kit/sdk — still matter: the agent can write Sandbox files, but ship checklist, schema, and auth policy stay in the repository your team reviews.

A practical adoption sequence

  1. Create separate application and environment keys in the same OpenAI org/project.
  2. Create the reusable agent; store OPENAI_AGENT_ID as config.
  3. Session create with environment: { type: "self_hosted", workspace_directory: "/workspace" }.
  4. Ship webhook with raw-body signature verification and Queue publish (idempotent on event ID).
  5. Ship private Queue consumer: session retrieve → getOrCreatecodex exec-server, plus vercel.json queue/v2beta trigger.
  6. Deploy, register production webhook, install OPENAI_WEBHOOK_SECRET, redeploy.
  7. Prove the loop: write a /workspace file, follow up to read it, delete session, confirm Sandbox cleanup.
  8. Add orphan Sandbox reconciliation and structured logs before customers hit the feature.

What not to do this week

  • Do not run Sandbox provisioning inline in the webhook response and hope OpenAI’s timeout is generous.
  • Do not put the application key inside the Sandbox “to simplify.”
  • Do not skip the queue/v2beta trigger because handleCallback “looks subscribed.”
  • Do not treat OpenAI session deletion as automatic Sandbox deletion.
  • Do not collapse this hosting seam into the harness ownership checklist — different failure modes, different owners.

App and webhook secrets stay in Functions; executor key stays in Sandbox

Sources

agentsvercelarchitecture
OTF SaaS Dashboard Kit

Ship the product, not the setup.

  • 11 production screens — auth, billing, team, analytics, settings
  • Real database, payments, and login — all wired on day 1
  • AI configs pre-tuned so your agent extends instead of regenerates