All guides
OpenAI

会话 Webhook

Checked 09/15/2026View original

AI translation, not an official translation. Refer to the original for technical details.

On this page

完整的文档索引请参阅 llms.txt。在页面 URL 后附加 .md 即可获取文档页面的 Markdown 版本。

使用 Webhook 响应会话状态变更,无需保持事件流持续开启。Webhook 处理程序可以启动或重新连接沙箱计算、更新您的应用程序,或触发工作流。

支持的事件

事件触发时机
agent.session.created会话已创建。
agent.session.action_required会话需要函数结果、初始环境连接或重新连接。
agent.session.in_progress会话开始处理一个轮次。
agent.session.idle会话处于空闲状态,已准备好接受更多输入。
agent.session.failed会话进入失败状态。

agent.session.action_required 事件包含会话 ID 以及值为 function_callenvironment_connectionrequired_action.type

{
  "type": "agent.session.action_required",
  "data": {
    "id": "sess_abc123",
    "required_action": { "type": "function_call" }
  }
}

请检索会话并查看 required_actions 以获取调用 ID、参数或环境 ID。Webhook 不包含这些详细信息。

设置 Webhook

请按照共享的 Webhook 设置指南创建端点并选择 Agents API 事件。保存端点的签名密钥以用于签名验证

接收事件

每当订阅的事件发生时,OpenAI 会发送一个已签名的 HTTP POST 请求:

{
  "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"
    }
  }
}

在预置沙箱之前,请先检索会话的当前状态。请参阅沙箱生命周期

启动执行器

对于自托管会话,agent.session.created 包含启动执行器所需的环境 ID 和连接 URL。将 ENVIRONMENT_ID 设置为 data.environment_id,将 REMOTE_URL 设置为 data.connect.remote_url。这与会话上作为 environment.remote_url 返回的 URL 相同。保存这两个值并在重新连接时复用:

CODEX_API_KEY="$OPENAI_ENVIRONMENT_KEY" \
codex exec-server \
  --remote "$REMOTE_URL" \
  --environment-id "$ENVIRONMENT_ID"

使用环境密钥作为 CODEX_API_KEY。请将您的应用程序 API 密钥保留在环境之外。

验证和处理事件

设置 OPENAI_API_KEYOPENAI_WEBHOOK_SECRET。对于 Python,请安装 fastapiuvicornopenai。对于 JavaScript,请安装 expressopenai

处理程序会验证签名并监听 8000 端口。设置 PORT 可更改端口。在生产环境中,请将耗时较长的工作加入队列

Webhook 处理程序

import express from "express";
import OpenAI from "openai";

const app = express();
const webhooks = new OpenAI({
  webhookSecret: process.env.OPENAI_WEBHOOK_SECRET,
});

app.post(
  "/webhooks/openai",
  express.raw({ type: "application/json" }),
  async (request, response) => {
    const payload = request.body.toString("utf8");
    try {
      await webhooks.webhooks.verifySignature(payload, request.headers);
    } catch {
      response.status(400).send("Invalid signature");
      return;
    }
    const event = JSON.parse(payload);
    if (event.type === "agent.session.idle") {
      const session = await webhooks.beta.agents.sessions.retrieve(
        event.data.id
      );
      console.log("session idle event:", session.id);
    } else {
      console.log("session event:", event.type, event.data.id);
    }
    response.sendStatus(200);
  }
);

app.listen(Number(process.env.PORT ?? 8000));
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")))
import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"

	"github.com/openai/openai-go/v3"
)

client := openai.NewClient()
http.HandleFunc("/webhooks/openai", func(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Invalid body", http.StatusBadRequest)
		return
	}
	if err := client.Webhooks.VerifySignature(body, r.Header); err != nil {
		http.Error(w, "Invalid signature", http.StatusBadRequest)
		return
	}
	var event struct {
		Type string `json:"type"`
		Data struct {
			ID string `json:"id"`
		} `json:"data"`
	}
	if err := json.Unmarshal(body, &event); err != nil {
		http.Error(w, "Invalid JSON", http.StatusBadRequest)
		return
	}
	if event.Type == "agent.session.idle" {
		session, err := client.Beta.Agents.Sessions.Get(r.Context(), event.Data.ID)
		if err != nil {
			http.Error(w, "Could not retrieve session", http.StatusInternalServerError)
			return
		}
		fmt.Println("session idle event:", session.ID)
	} else {
		fmt.Println("session event:", event.Type, event.Data.ID)
	}
	w.WriteHeader(http.StatusOK)
})
port := os.Getenv("PORT")
if port == "" {
	port = "8000"
}
if err := http.ListenAndServe(":"+port, nil); err != nil {
	panic(err)
}
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.http.Headers;
import com.openai.errors.InvalidWebhookSignatureException;
import com.openai.models.webhooks.WebhookVerificationParams;
import com.sun.net.httpserver.HttpServer;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;

OpenAIClient client = OpenAIOkHttpClient.fromEnv();
var json = new JsonMapper();
int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8000"));
var server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext(
    "/webhooks/openai",
    exchange -> {
      try (exchange) {
        if (!exchange.getRequestMethod().equals("POST")) {
          exchange.sendResponseHeaders(405, -1);
          return;
        }
        String payload =
            new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
        try {
          client
              .webhooks()
              .verifySignature(
                  WebhookVerificationParams.builder()
                      .payload(payload)
                      .headers(Headers.builder().putAll(exchange.getRequestHeaders()).build())
                      .build());
        } catch (InvalidWebhookSignatureException e) {
          exchange.sendResponseHeaders(400, -1);
          return;
        }
        var event = json.readTree(payload);
        if (event.path("type").asText().equals("agent.session.idle")) {
          var session =
              client
                  .beta()
                  .agents()
                  .sessions()
                  .retrieve(event.path("data").path("id").asText());
          System.out.println("session idle event: " + session.id());
        } else {
          System.out.println(
              "session event: "
                  + event.path("type").asText()
                  + " "
                  + event.path("data").path("id").asText());
        }
        exchange.sendResponseHeaders(200, -1);
      }
    });
server.start();
require "openai"
require "webrick"
require "json"

client = OpenAI::Client.new
server = WEBrick::HTTPServer.new(Port: Integer(ENV.fetch("PORT", "8000")))
server.mount_proc "/webhooks/openai" do |request, response|
  if request.request_method != "POST"
    response.status = 405
    next
  end
  payload = request.body
  begin
    client.webhooks.verify_signature(payload, request.header.transform_values(&:first))
  rescue OpenAI::Errors::InvalidWebhookSignatureError
    response.status = 400
    response.body = "Invalid signature"
    next
  end
  event = JSON.parse(payload)
  if event["type"] == "agent.session.idle"
    session = client.beta.agents.sessions.retrieve(event.fetch("data").fetch("id"))
    puts "session idle event: #{session.id}"
  else
    puts "session event: #{event["type"]} #{event.dig("data", "id")}"
  end
  response.status = 200
end
trap("INT") { server.shutdown }
server.start

环境连接事件

当初始或后续输入需要一个已断开连接的自托管执行器时,API 会添加 environment_connection 所需操作,并在等待连接之前发出 agent.session.action_required

请检索会话并确认 required_actions 仍在请求连接。使用 session.environment.idsession.environment.remote_url 启动执行器。此 Webhook 不包含 connect.remote_url。如果执行器在等待超时之前完成连接,API 将清除所需操作并恢复提交,无需客户端重新提交。

API 最多等待五分钟以建立连接。后续输入请求在此等待期间可保持开启状态。请相应配置客户端和代理的超时时间。agent.session.in_progress 确认的是执行已启动,而非 API 正在等待连接。

如果等待超时,提交将失败。初始输入可能异步失败并使会话进入 failed 状态。连接等待不提供持久化的输入队列。进程崩溃或客户端断开连接可能需要重试。

会话和轮次结果

agent.session.idle 表示会话已准备好接受更多输入,而非其最后一个轮次已成功。请检查该轮次的状态,或观察会话流上的 agent.session.turn.completedagent.session.turn.failedagent.session.turn.cancelled。已完成的轮次仍可能包含失败的工具调用。请检查工具结果和智能体的最终响应。

agent.session.failed 报告的是会话失败,而非每次轮次失败。会话删除没有对应的 Webhook,也不会停止提供商的计算资源。