Webhooks מאפשרים ל-Gemini API לשלוח התראות בזמן אמת לשרת שלכם כשפעולות אסינכרוניות או פעולות ארוכות טווח (LRO) מסתיימות. השיטה הזו מחליפה את הצורך לשלוח בקשות ל-API כדי לקבל עדכוני סטטוס, וכך מקטינה את זמן האחזור ואת התקורה.
אפשר להשתמש ב-Webhooks לפעולות כמו משימות Batch, אינטראקציות ויצירת סרטונים.
איך זה עובד
במקום לבצע סקר GET /operations שוב ושוב כדי לבדוק אם משימה הסתיימה, אפשר להגדיר Webhooks של Gemini API כדי לשלוח בקשת HTTP POST לכתובת ה-URL של רכיב ה-listener מיד כשמופעל אירוע.
Gemini API תומך בשתי דרכים להגדרת ווּבְּהוּק:
- Static webhooks: נקודות קצה ברמת הפרויקט שהוגדרו באמצעות Gemini WebhookService API. מתאים לשילובים גלובליים (לדוגמה, שליחת התראות ל-Slack, סנכרון מסד נתונים וכו').
- וווב-הוקים דינמיים: שינויים ברמת הבקשה שמעבירים webhook URL במטען הייעודי (payload) של ההגדרה של קריאה ספציפית למשרות. אידיאלי לניתוב משימות ספציפיות לנקודות קצה ייעודיות.
Webhooks סטטיים
הרישום של וווב-הוקים סטטיים מתבצע עבור פרויקט שלם, והם מופעלים לכל אירוע תואם.
יצירת webhook
אפשר ליצור נקודות קצה באמצעות ה-SDK או REST API.
חשוב: כשיוצרים webhook, ה-API מחזיר סוד חתימה רק פעם אחת. כדי לאמת חתימות בהמשך, צריך לאחסן את המפתח הזה בצורה מאובטחת (למשל, במשתני הסביבה). אם תאבדו את הסוד לחתימה, תצטרכו להחליף אותו.
Python
from google import genai
client = genai.Client()
webhook = client.webhooks.create(
name="MyBatchWebhook",
subscribed_events=["batch.succeeded", "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.succeeded", "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: $GEMINI_API_KEY" \
-d '{
"name": "MyBatchWebhook",
"uri": "https://my-api.com/gemini-callback",
"subscribed_events": ["batch.succeeded", "batch.failed"]
}'
פרטים על הגדרת השרת לקבלת נתונים מופיעים בקטע טיפול בבקשות של webhook.
קבלת webhook
אחזור פרטים על webhook ספציפי לפי שם המשאב שלו.
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: $GEMINI_API_KEY"
הצגת רשימת ה-webhooks
רשימה של כל ה-webhook שהוגדרו בפרויקט הנוכחי, עם אפשרות להוספת מספור עמודים.
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: $GEMINI_API_KEY"
עדכון webhook
עדכון המאפיינים של webhook קיים, כמו שם התצוגה, ה-URI של היעד או האירועים שנרשמתם אליהם.
Python
from google import genai
client = genai.Client()
updated_webhook = client.webhooks.update(
id="<your_webhook_id>",
subscribed_events=["batch.succeeded", "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.succeeded", "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: $GEMINI_API_KEY" \
-d '{
"subscribed_events": ["batch.succeeded", "batch.failed", "batch.cancelled"]
}'
מחיקת webhook
הסרה של נקודת קצה של webhook מהפרויקט. הפעולה הזו תמנע מסירת אירועים עתידיים לנקודת הקצה הזו.
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: $GEMINI_API_KEY"
סיבוב של סוד חתימה
סיבוב של ערך ה-Secret לחתימה של webhook. אתם יכולים להגדיר אם סודות שהיו פעילים בעבר יבוטלו באופן מיידי או אחרי תקופת חסד של 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: $GEMINI_API_KEY" \
-d '{
"revocation_behavior": "REVOKE_PREVIOUS_SECRETS_AFTER_H24"
}'
טיפול בבקשות webhook בשרת
כשמתרחש אירוע שנרשמתם לקבלת עדכונים לגביו, כתובת ה-webhook URL שלכם תקבל בקשת HTTP POST. נקודת הקצה צריכה להגיב עם קוד סטטוס 2xx תוך כמה שניות כדי למנוע ניסיון חוזר. כדי לוודא שהבקשות יועברו, Gemini API מבצע ניסיון חוזר אוטומטי של בקשות שנכשלו למשך 24 שעות באמצעות השהיה מעריכית לפני ניסיון חוזר (exponential backoff).
Gemini פועל בהתאם למפרט Standard Webhooks לגבי כותרות אבטחה. מאמתים את מטען הנתונים בשרת באמצעות החתימות של הכותרת החתומה ומפתח החתימה הסטטי השמור. מידע על מטען הייעודי (payload) מופיע בקטע Webhook envelope.
דוגמה לשימוש ב-Flask בשביל מאזין 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") == "batch.succeeded":
print(f"Batch completed! ID: {event["data"]["id"]}")
if event["data"].get("output_file_uri"):
# For batch jobs with input file
print(f"Batch file: {event["data"]["output_file_uri"]}")
elif (event.type == "video.generated"):
print(f"Video generated! URI: {event["data"]["output_file_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>;
console.log(`Event type: ${event.type}, data: ${JSON.stringify(event.data)}`);
// Process thin payload contents
if (event.type === "batch.succeeded") {
console.log(`Batch completed! ID: ${event.data.id}`);
if (event.data.output_file_uri) {
// For batch jobs with input file
console.log(`Batch file: ${event.data.output_file_uri}`);
}
} else if (event.type === "video.generated") {
console.log(`Video generated! URI: ${event.data.output_file_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");
});
Webhooks דינמיים
בעזרת webhooks דינמיים, אפשר לקשר נקודת קצה של webhook להגדרת בקשה ספציפית, וזה אידיאלי לתורים של תיאום בין נציגים. ב-webhooks דינמיים נעשה שימוש בחתימות 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.8-flash",
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.8-flash",
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.8-flash:batchCreate" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: $GEMINI_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)
בקשות דינמיות של webhook פולטות חתימה של JSON Web Token (JWT). המאזין צריך לחלץ את החתימה ולאמת אותה באמצעות נקודות הקצה של אישור הציבורי של 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" });
}
);
});
מעטפת webhook
כדי למנוע עומס על רוחב הפס, ה-webhooks של Gemini משתמשים במודל של מטען ייעודי (payload) דק כדי להעביר נתונים. ההעברות שולחות snapshot שמכיל פרטי סטטוס ונקודות להפניה לתוצאות, ולא את קובץ הפלט הגולמי עצמו.
דוגמה לפורמט של מטען ייעודי (payload):
{
"type": "batch.succeeded",
"version": "v1",
"timestamp": "2026-01-22T12:00:00Z",
"data": {
"id": "batch_123456",
"output_file_uri": "gs://my-bucket/results.jsonl"
}
}
הפניה לקטלוג האירועים
האירועים הבאים מופעלים עבור משרות תומכות:
| סוג אירוע | טריגר | פריט מטען ייעודי (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 |
הפעולה הממושכת ב-API של האינטראקציות הסתיימה בהצלחה | id |
interaction.failed |
הפעולה LRO ב-API של האינטראקציות נכשלה (שגיאת מערכת או שגיאת אימות). | id, error_code, error_message |
interaction.cancelled |
בוטלה פעולת LRO בממשק API של אינטראקציות | id |
video.generated |
תהליך LRO של יצירת הסרטון הושלם. | id, output_file_uri, file_name |
שיטות מומלצות
כדי להבטיח פעולה אמינה וניתנת להרחבה:
- בדיקה קפדנית של הגנה מפני שידור חוזר: כל הבקשות כוללות כותרת
webhook-timestamp. חשוב תמיד לאמת את חותמת הזמן הזו בשכבת הגדרות השרת כדי לדחות נתוני payload ישנים יותר מ5 דקות (כדי לצמצם את הסיכון למתקפות שידור חוזר). - עיבוד אסינכרוני: תגובה עם
2xx OKמיד לאחר זיהוי חתימה תקינה, והוספת פעולות הניתוח לתור באופן פנימי. זמני המתנה ארוכים של המאזינים יפעילו מחזור של ניסיונות מסירה. - טיפול בהסרת כפילויות: ווּבּהוּקים רגילים מספקים את הנתונים בשיטת 'לפחות פעם אחת'. כדאי להשתמש בכותרת
webhook-idconsistent כדי לטפל בכפילויות פוטנציאליות בזרימות עם עומס גבוה יותר.
מה השלב הבא?
- Batch API: שימוש ב-webhook כדי להפוך נקודות קצה בעלות נפח גבוה לאוטומטיות.