เว็บฮุคช่วยให้ Gemini API ส่งการแจ้งเตือนแบบเรียลไทม์ไปยังเซิร์ฟเวอร์ของคุณเมื่อการดำเนินการแบบไม่พร้อมกันหรือการดำเนินการที่ใช้เวลานาน (LRO) เสร็จสมบูรณ์ ซึ่งจะมาแทนที่ความจำเป็นในการโพล API เพื่อดูการอัปเดตสถานะ จึงช่วยลดเวลาในการตอบสนองและค่าใช้จ่าย
เว็บฮุคพร้อมใช้งานสำหรับการดำเนินการต่างๆ เช่น งานแบบกลุ่ม การโต้ตอบ และ การสร้างวิดีโอ
วิธีการทำงาน
แทนที่จะโพล GET /operations ซ้ำๆ เพื่อตรวจสอบว่างานเสร็จสมบูรณ์แล้วหรือไม่ คุณสามารถกำหนดค่าเว็บฮุคของ Gemini API ให้ส่งคำขอ HTTP POST ไปยัง URL ของ Listener ทันทีเมื่อมีการทริกเกอร์เหตุการณ์
Gemini API รองรับการกำหนดค่าเว็บฮุค 2 วิธีดังนี้
- เว็บฮุคแบบคงที่: อุปกรณ์ปลายทางระดับโปรเจ็กต์ที่กำหนดค่า ด้วย Gemini WebhookService API เหมาะสำหรับการผสานรวมทั่วโลก (เช่น การแจ้งเตือน Slack, การซิงค์ฐานข้อมูล ฯลฯ)
- เว็บฮุคแบบไดนามิก: การลบล้างระดับคำขอโดยส่ง URL ของเว็บฮุคในเพย์โหลดการกำหนดค่าของการเรียกงานที่เฉพาะเจาะจง เหมาะสำหรับการกำหนดเส้นทางงานที่เฉพาะเจาะจงไปยังอุปกรณ์ปลายทางเฉพาะ
เว็บฮุคแบบคงที่
เว็บฮุคแบบคงที่จะลงทะเบียนสำหรับทั้ง โปรเจ็กต์ และทริกเกอร์สำหรับเหตุการณ์ที่ตรงกัน
สร้างเว็บฮุค
คุณสามารถสร้างอุปกรณ์ปลายทางได้โดยใช้ SDK หรือ REST API
สำคัญ: เมื่อสร้างเว็บฮุค API จะแสดงข้อมูลลับในการลงนาม เพียงครั้งเดียว คุณต้องจัดเก็บข้อมูลนี้อย่างปลอดภัย (เช่น ในตัวแปรสภาพแวดล้อม) เพื่อยืนยันลายเซ็นในภายหลัง หากข้อมูลลับในการลงนามสูญหาย คุณจะต้อง หมุนเวียนข้อมูลลับดังกล่าว
Python
from google import genai
client = genai.Client()
webhook = client.webhooks.create(
name="MyBatchWebhook",
subscribed_events=["batch.completed", "batch.failed"],
uri="https://my-api.com/gemini-callback",
)
# Store webhook.new_signing_secret securely
webhook_secret = webhook.new_signing_secret
print(f"Created webhook: {webhook.name}, {webhook.id}")
JavaScript
import { GoogleGenAI } from "@google/genai";
const client = new GoogleGenAI();
async function createWebhook() {
const webhook = await client.webhooks.create({
name: "MyBatchWebhook",
subscribed_events: ["batch.completed", "batch.failed"],
uri: "https://my-api.com/gemini-callback",
});
// Store webhook.signingSecret securely
const webhookSecret = webhook.new_signing_secret;
console.log(`Created webhook: ${webhook.name}, ${webhook.id}`);
}
createWebhook();
REST
curl -X POST \
"https://generativelanguage.googleapis.com/v1/webhooks" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: $GOOGLE_API_KEY" \
-d '{
"name": "MyBatchWebhook",
"uri": "https://my-api.com/gemini-callback",
"subscribed_events": ["batch.completed", "batch.failed"]
}'
ดูรายละเอียดเกี่ยวกับการตั้งค่าเซิร์ฟเวอร์เพื่อรับข้อมูลได้ที่ส่วน จัดการคำขอเว็บฮุค
รับเว็บฮุค
ดึงข้อมูลรายละเอียดเกี่ยวกับเว็บฮุคที่เฉพาะเจาะจงตามชื่อทรัพยากร
Python
from google import genai
client = genai.Client()
webhook = client.webhooks.get(id="<your_webhook_id>")
print(f"Webhook: {webhook.name}")
print(f"URI: {webhook.uri}")
print(f"Events: {webhook.subscribed_events}")
JavaScript
import { GoogleGenAI } from "@google/genai";
const client = new GoogleGenAI(); // Assumes process.env.GEMINI_API_KEY is set
async function getWebhook() {
const webhook = await client.webhooks.get("<your_webhook_id>");
console.log(`Webhook: ${webhook.name}`);
console.log(`URI: ${webhook.uri}`);
console.log(`Events: ${webhook.subscribed_events}`);
}
getWebhook();
REST
curl -X GET \
"https://generativelanguage.googleapis.com/v1/webhooks/<your_webhook_id>" \
-H "x-goog-api-key: $GOOGLE_API_KEY"
แสดงรายการเว็บฮุค
แสดงรายการเว็บฮุคทั้งหมดที่กำหนดค่าไว้สำหรับโปรเจ็กต์ปัจจุบัน พร้อมการแบ่งหน้า (ไม่บังคับ)
Python
from google import genai
client = genai.Client()
webhooks = client.webhooks.list()
for wh in webhooks:
print(f"{wh.id}: {wh.name} -> {wh.uri}")
JavaScript
import { GoogleGenAI } from "@google/genai";
const client = new GoogleGenAI();
async function listWebhooks() {
const webhooks = await client.webhooks.list();
for (const wh of webhooks) {
console.log(`${wh.id}: ${wh.name} -> ${wh.uri}`);
}
}
listWebhooks();
REST
curl -X GET \
"https://generativelanguage.googleapis.com/v1/webhooks" \
-H "x-goog-api-key: $GOOGLE_API_KEY"
อัปเดตเว็บฮุค
อัปเดตพร็อพเพอร์ตี้ของเว็บฮุคที่มีอยู่ เช่น ชื่อที่แสดง, URI เป้าหมาย หรือเหตุการณ์ที่สมัครรับข้อมูล
Python
from google import genai
client = genai.Client()
updated_webhook = client.webhooks.update(
id="<your_webhook_id>",
subscribed_events=["batch.completed", "batch.failed", "batch.cancelled"],
)
print(f"Updated webhook: {updated_webhook.name}")
JavaScript
import { GoogleGenAI } from "@google/genai";
const client = new GoogleGenAI();
async function updateWebhook() {
const updatedWebhook = await client.webhooks.update(
"<your_webhook_id>",
{
subscribed_events: ["batch.completed", "batch.failed", "batch.cancelled"],
}
);
console.log(`Updated webhook: ${updatedWebhook.name}`);
}
updateWebhook();
REST
curl -X PATCH \
"https://generativelanguage.googleapis.com/v1/webhooks/<your_webhook_id>" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: $GOOGLE_API_KEY" \
-d '{
"subscribed_events": ["batch.completed", "batch.failed", "batch.cancelled"]
}'
ลบเว็บฮุค
นำอุปกรณ์ปลายทางของเว็บฮุคออกจากโปรเจ็กต์ ซึ่งจะเป็นการหยุดการส่งเหตุการณ์ในอนาคตไปยังอุปกรณ์ปลายทางนั้น
Python
from google import genai
client = genai.Client()
client.webhooks.delete(id="<your_webhook_id>")
print("Webhook deleted.")
JavaScript
import { GoogleGenAI } from "@google/genai";
const client = new GoogleGenAI();
async function deleteWebhook() {
await client.webhooks.delete("<your_webhook_id>");
console.log("Webhook deleted.");
}
deleteWebhook();
REST
curl -X DELETE \
"https://generativelanguage.googleapis.com/v1/webhooks/<your_webhook_id>" \
-H "x-goog-api-key: $GOOGLE_API_KEY"
หมุนเวียนข้อมูลลับในการลงนาม
หมุนเวียนข้อมูลลับในการลงนามสำหรับเว็บฮุค คุณสามารถกำหนดค่าว่าจะเพิกถอนข้อมูลลับที่ใช้งานก่อนหน้านี้ทันทีหรือหลังจากระยะเวลาผ่อนผัน 24 ชั่วโมง
สำคัญ: ระบบจะแสดงข้อมูลลับในการลงนามใหม่เพียงครั้งเดียวในเวลาที่หมุนเวียน โปรดจัดเก็บข้อมูลลับดังกล่าวอย่างปลอดภัยก่อนที่จะอัปเดตตรรกะการยืนยัน
Python
from google import genai
from google.genai import types
client = genai.Client()
response = client.webhooks.rotate_signing_secret(
id="<your_webhook_id>",
revocation_behavior="REVOKE_PREVIOUS_SECRETS_AFTER_H24",
)
# Store response.secret securely, then update your server's verification config
print("New signing secret generated. Update your server configuration.")
JavaScript
import { GoogleGenAI } from "@google/genai";
const client = new GoogleGenAI();
async function rotateSigningSecret() {
const response = await client.webhooks.rotateSigningSecret(
"<your_webhook_id>",
{
revocation_behavior: "REVOKE_PREVIOUS_SECRETS_AFTER_H24",
}
);
// Store response.secret securely, then update your server's verification config
console.log("New signing secret generated. Update your server configuration.");
}
rotateSigningSecret();
REST
curl -X POST \
"https://generativelanguage.googleapis.com/v1/webhooks/<your_webhook_id>/rotate_secret" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: $GOOGLE_API_KEY" \
-d '{
"revocation_behavior": "REVOKE_PREVIOUS_SECRETS_AFTER_H24"
}'
จัดการคำขอเว็บฮุคในเซิร์ฟเวอร์
เมื่อเกิดเหตุการณ์ที่คุณสมัครรับข้อมูล URL ของเว็บฮุคจะได้รับคำขอ HTTP POST อุปกรณ์ปลายทางต้องตอบกลับด้วยรหัสสถานะ 2xx ภายใน 2-3 วินาทีเพื่อหลีกเลี่ยงการลองอีกครั้ง Gemini API จะลองส่งคำขอที่ล้มเหลวอีกครั้งโดยอัตโนมัติเป็นเวลา 24 ชั่วโมงโดยใช้ Exponential Backoff เพื่อให้มั่นใจว่ามีการส่งข้อมูล
Gemini ปฏิบัติตามข้อกำหนดของเว็บฮุคมาตรฐานสำหรับ ส่วนหัวด้านความปลอดภัยอย่างเคร่งครัด ยืนยันเพย์โหลดในเซิร์ฟเวอร์โดยใช้ลายเซ็นส่วนหัวที่ลงนามและข้อมูลลับในการลงนามแบบคงที่ที่จัดเก็บไว้ ดูข้อมูลเพย์โหลดได้ที่ส่วนซองจดหมายของเว็บฮุค Webhook envelope
ตัวอย่างการใช้ Flask สำหรับ Listener HTTP
Python
# pip install flask standardwebhooks
import os
from flask import Flask, request, jsonify
# Standard verification wrapper for Standard Webhook Headers
from standardwebhooks.webhooks import Webhook, WebhookVerificationError
app = Flask(__name__)
SIGNING_SECRET = os.environ.get('WEBHOOK_SIGNING_SECRET')
@app.route('/gemini-callback', methods=['POST'])
def gemini_callback():
payload = request.get_data(as_text=True)
headers = request.headers
try:
wh = Webhook(SIGNING_SECRET)
event = wh.verify(payload, headers)
except WebhookVerificationError as e:
return jsonify({"error": "Signature invalid"}), 400
# Process thin payload contents
if event.get("type") in ("batch.completed", "video.generated"):
uri = event['data']['output_file_uri']
print(f"Batch finished! Results at: {uri}")
return jsonify({"status": "received"}), 200
if __name__ == "__main__":
app.run(port=8000)
JavaScript
// npm install standardwebhooks
import { Webhook } from "standardwebhooks";
import express from "express";
const app = express();
const client = new GoogleGenAI({ webhookSecret: process.env.WEBHOOK_SIGNING_SECRET });
// Don't use express.json() because signature verification needs the raw text body
app.use(express.text({ type: "application/json" }));
app.post("/gemini-callback", async (req, res) => {
const payload = await req.text();
const headers: Record<string, string> = {};
req.headers.forEach((value, key) => {
headers[key] = value;
});
try {
const wh = new Webhook(process.env.WEBHOOK_SIGNING_SECRET);
const event = wh.verify(payload, headers) as Record<string, any>;
// Process thin payload contents
if (event.type === "batch.completed" || event.type === "video.generated") {
const uri = event.data.output_file_uri;
console.log(`Job finished! Results at: ${uri}`);
}
res.status(200).json({ status: "received" });
} catch (e) {
console.error("Webhook verification failed:", e);
res.status(400).send("Invalid signature");
}
});
app.listen(8000, () => {
console.log("Webhook server is running on port 8000");
});
เว็บฮุคแบบไดนามิก
เว็บฮุคแบบไดนามิกช่วยให้คุณผูกอุปกรณ์ปลายทางของเว็บฮุคกับการกำหนดค่าคำขอที่เฉพาะเจาะจง ซึ่งเหมาะสำหรับคิวการจัดระเบียบเอเจนต์ เว็บฮุคแบบไดนามิกใช้ลายเซ็น JWKS คีย์สาธารณะแบบอสมมาตรแทนข้อมูลลับแบบสมมาตร
ส่งคำขอแบบไดนามิก
เพิ่ม webhook_config เมื่อทริกเกอร์งานแบบอะซิงโครนัส (เช่น การสร้าง Batch)
Python
from google import genai
from google.genai import types
client = genai.Client()
file_batch_job = client.batches.create(
model="gemini-3-flash-preview",
src="files/uploaded_file_id",
config={
"display_name": "My Setup",
"webhook_config": {
"uris": ["https://my-api.com/gemini-webhook-dynamic"],
"user_metadata":{"job_group": "nightly-eval", "priority": "high"}
}
}
)
JavaScript
import { GoogleGenAI } from "@google/genai";
const client = new GoogleGenAI();
async function createBatchWithWebhook() {
const fileBatchJob = await client.batches.create({
model: "gemini-3-flash-preview",
src: "files/uploaded_file_id",
config: {
displayName: "My Setup",
webhookConfig: {
uris: ["https://my-api.com/gemini-webhook-dynamic"],
user_metadata: {"job_group": "nightly-eval", "priority": "high"}
},
},
});
}
REST
curl -X POST \
"https://generativelanguage.googleapis.com/v1/models/gemini-3-flash-preview:batchCreate" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: $GOOGLE_API_KEY" \
-d '{
"src": "files/uploaded_file_id",
"config": {
"display_name": "My Setup",
"webhook_config": {
"uris": ["https://my-api.com/gemini-webhook-dynamic"],
"user_metadata": {"job_group": "nightly-eval", "priority": "high"}
}
}
}'
ยืนยันลายเซ็นแบบไดนามิก (JWKS)
คำขอเว็บฮุคแบบไดนามิกจะแสดงลายเซ็น JSON Web Token (JWT) Listener ต้องแยกข้อมูลลายเซ็นและยืนยันโดยใช้อุปกรณ์ปลายทางใบรับรองสาธารณะของ Google
Python
import jwt
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
# Google public cert list endpoint
JWKS_URI = "https://generativelanguage.googleapis.com/.well-known/jwks.json"
def load_google_public_key(kid):
response = requests.get(JWKS_URI).json()
for key_item in response.get('keys', []):
if key_item.get('kid') == kid:
# Convert JWK to Cert wrapper
return jwt.algorithms.RSAAlgorithm.from_jwk(key_item)
return None
@app.route('/gemini-webhook-dynamic', methods=['POST'])
def dynamic_handler():
payload = request.get_data(as_text=True)
headers = request.headers
token = headers.get('Webhook-Signature')
if not token:
return jsonify({"error": "No signature header"}), 400
try:
# Extract kid from JWT header
unverified_headers = jwt.get_unverified_header(token)
pub_key = load_google_public_key(unverified_headers.get('kid'))
if not pub_key:
return jsonify({"error": "Key cert not found"}), 400
# Verify Signature against expected audience (e.g., your project client ID)
event = jwt.decode(
token,
pub_key,
algorithms=["RS256"],
audience="your-configured-audience"
)
except Exception as e:
return jsonify({"error": "Invalid Dynamic signature", "details": str(e)}), 400
print("Verified Dynamic payload success.")
return jsonify({"status": "received"}), 200
JavaScript
import { GoogleGenAI } from "@google/genai";
import express from "express";
import jwt from "jsonwebtoken";
import jwksClient from "jwks-rsa";
const app = express();
app.use(express.text({ type: 'application/json' }));
const client = jwksClient({
jwksUri: "https://generativelanguage.googleapis.com/.well-known/jwks.json"
});
function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
const signingKey = key.getPublicKey();
callback(null, signingKey);
});
}
app.post('/gemini-webhook-dynamic', (req, res) => {
const token = req.headers['webhook-signature'];
if (!token) {
return res.status(400).json({ error: "No signature header" });
}
jwt.verify(
token,
getKey,
{
algorithms: ["RS256"],
audience: "your-configured-audience"
},
(err, decoded) => {
if (err) {
return res.status(400).json({ error: "Invalid Dynamic signature", details: err.message });
}
console.log("Verified Dynamic payload success.");
res.status(200).json({ status: "received" });
}
);
});
ซองจดหมายของเว็บฮุค
เว็บฮุคของ Gemini ใช้โมเดลเพย์โหลดขนาดเล็ก เพื่อส่งข้อมูลเพื่อหลีกเลี่ยงการติดขัดของแบนด์วิดท์ โดยการส่งจะส่งสแนปช็อตที่มีรายละเอียดสถานะและตัวชี้ไปยังผลลัพธ์แทนที่จะส่งไฟล์เอาต์พุตดิบเอง
ตัวอย่างรูปแบบเพย์โหลด
{
"type": "batch.completed",
"version": "v1",
"timestamp": "2026-01-22T12:00:00Z",
"data": {
"id": "batch_123456",
"output_file_uri": "gs://my-bucket/results.jsonl",
"error_count": 0
}
}
ข้อมูลอ้างอิงแคตตาล็อกเหตุการณ์
ระบบจะทริกเกอร์เหตุการณ์ต่อไปนี้สำหรับงานที่รองรับ
| ประเภทของกิจกรรม | ทริกเกอร์ | รายการเพย์โหลด (data) |
|---|---|---|
batch.succeeded |
การประมวลผลเสร็จสมบูรณ์แล้ว | id, output_file_uri |
batch.cancelled |
ผู้ใช้ยกเลิกคำขอ | id |
batch.expired |
ระบบไม่ได้ประมวลผล (เสร็จสิ้น) กลุ่มภายในกรอบเวลา 24 ชั่วโมง | id |
batch.failed |
งานแบบกลุ่มล้มเหลว (ข้อผิดพลาดของระบบหรือข้อผิดพลาดในการตรวจสอบ) | id, error_code, error_message |
interaction.requires_action |
การเรียกใช้ฟังก์ชัน ผู้ใช้ต้องดำเนินการบางอย่าง | id |
interaction.completed |
LRO ใน Interactions API สำเร็จ | id |
interaction.failed |
LRO ใน Interactions API ล้มเหลว (ข้อผิดพลาดของระบบหรือข้อผิดพลาดในการตรวจสอบ) | id, error_code, error_message |
interaction.cancelled |
LRO ใน Interactions API ยกเลิก | id |
video.generated |
LRO การสร้างวิดีโอเสร็จสมบูรณ์แล้ว | file_id, video_uri |
แนวทางปฏิบัติแนะนำ
สิ่งที่ควรทำเพื่อให้การดำเนินการเชื่อถือได้และรองรับการปรับขนาด
- การตรวจสอบการป้องกันการเล่นซ้ำอย่างเข้มงวด: คำขอทั้งหมดมีส่วนหัว
webhook-timestampตรวจสอบการประทับเวลาในเลเยอร์การกำหนดค่าเซิร์ฟเวอร์เสมอเพื่อปฏิเสธเพย์โหลดที่เก่ากว่า 5 นาที (เพื่อลดการโจมตีแบบเล่นซ้ำ) - ประมวลผลแบบอะซิงโครนัส: ตอบกลับด้วย
2xx OKทันทีเมื่อตรวจพบข้อมูลลับในการลงนามที่ถูกต้อง และจัดคิวการดำเนินการแยกวิเคราะห์ภายใน เวลาพัก Listener ที่นานเกินไปจะทริกเกอร์รอบการลองส่งอีกครั้ง - การจัดการการหักล้างข้อมูลที่ซ้ำกัน: เว็บฮุคมาตรฐานจะส่ง "อย่างน้อย 1 ครั้ง" ใช้ส่วนหัว
webhook-idที่สอดคล้องกันเพื่อจัดการข้อมูลที่ซ้ำกันที่อาจเกิดขึ้นในโฟลว์ที่มีการติดขัดสูง
ขั้นตอนต่อไปคืออะไร
- Batch API: ใช้เว็บฮุคเพื่อทำให้การดำเนินการกับอุปกรณ์ปลายทางที่มีปริมาณมากเป็นไปโดยอัตโนมัติ