OpenAI 会话 Webhook

2026-11-06 14:59 更新

Webhook可在不保持事件流的情况下响应会话状态变化,用于启动或重连沙箱、更新应用或触发工作流。

支持的事件

事件 触发时机
agent.session.created 会话创建
agent.session.action_required 需要函数结果、环境连接或计算机使用批准
agent.session.in_progress 会话开始处理轮次
agent.session.idle 空闲,可接收更多输入
agent.session.failed 会话进入失败状态

agent.session.action_required 含会话ID及 required_action.type,可为 function_call、environment_connection、computer_use_approval_request:

来源示例 1(json):

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

Webhook省略调用ID、参数、环境ID和批准细节,需重新读取会话 required_actions。计算机使用的嵌套 request.type 区分浏览器源访问或认证。把当前请求展示给用户,通过会话事件端点提交其回应,结构见计算机使用。

配置 Webhook

按通用配置指南创建端点并选Agents API事件,保存端点秘密以验证签名。

接收事件

订阅事件发生时,OpenAI发送带签名的HTTP POST:

来源示例 2(json):

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

创建沙箱前重新读取会话当前状态,见生命周期Webhook处理。

启动执行器

自托管会话的 agent.session.created 含环境ID及连接URL。将 data.environment_id 保存为 ENVIRONMENT_ID,data.connect.remote_url 保存为 REMOTE_URL;它与会话 environment.remote_url 相同,重连时复用:

来源示例 3(bash):

## [中文] 示例 3:启动执行器。
## [中文] 在执行环境中启动执行器,连接值来自创建会话的响应;使用环境密钥,不传应用密钥。
CODEX_API_KEY="$OPENAI_ENVIRONMENT_KEY" \
codex exec-server \
  --remote "$REMOTE_URL" \
  --environment-id "$ENVIRONMENT_ID"

以环境密钥作为 CODEX_API_KEY,应用API密钥保留在环境外。

验证与处理事件

设置 OPENAI_API_KEY、OPENAI_WEBHOOK_SECRET。Python安装 fastapi、uvicorn、openai,JavaScript安装 express、openai。

处理器验证签名,默认监听8000端口,可通过 PORT 修改。生产环境应把慢工作入队。

来源示例 4(javascript):

// [中文] 示例 4:验证与处理事件。
// [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
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));

来源示例 5(python):

## [中文] 示例 5:验证与处理事件。
## [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
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")))

来源示例 6(go):

// [中文] 示例 6:验证与处理事件。
// [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
// [中文] Go HTTP 处理器读取原始请求体验证 webhook 签名,按事件分派并及时回应,慢工作应入队。
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)
}

来源示例 7(java):

// [中文] 示例 7:验证与处理事件。
// [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
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();

来源示例 8(ruby):

## [中文] 示例 8:验证与处理事件。
## [中文] 读取当前会话状态和 required_actions;历史项目不代表动作仍待处理。
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.id 和 session.environment.remote_url 启动执行器。此webhook没有 connect.remote_url。截止前连上,API会清除动作并恢复原提交,无需客户端重发。

最多等待五分钟,后续输入请求可在这段时间保持打开,客户端与代理超时需相应配置。agent.session.in_progress 表示已经开始执行,不表示正在等连接。

超时后提交失败;初始输入可异步失败使会话为 failed。连接等待不是持久化输入队列,进程崩溃或客户端断连后可能需要重试。

会话与轮次结果

agent.session.idle 只表示可接收输入,不说明上一轮成功。应检查轮次状态,或在会话流观察 agent.session.turn.completed、agent.session.turn.failed、agent.session.turn.cancelled,完成后也检查工具结果和最终回复。

agent.session.failed 报告会话失败,不覆盖所有失败轮次。删除会话没有对应webhook,也不会停止提供商计算资源。

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号