Mit Webhooks kann die Gemini API Echtzeitbenachrichtigungen an Ihren Server senden, wenn asynchrone Vorgänge oder Vorgänge mit langer Ausführungszeit (Long-Running Operations, LROs) abgeschlossen sind. Dadurch entfällt das Abrufen von Statusaktualisierungen über die API, was die Latenz und den Aufwand reduziert.
Webhooks sind für Vorgänge wie Batch-Jobs, Interaktionen und die Videogenerierung verfügbar.
Funktionsweise
Anstatt GET /operations wiederholt abzufragen, um zu prüfen, ob ein Job abgeschlossen ist, können Sie Gemini API-Webhooks so konfigurieren, dass bei einem Ereignistrigger sofort eine HTTP-POST-Anfrage an Ihre Listener-URL gesendet wird.
Die Gemini API unterstützt zwei Möglichkeiten zum Konfigurieren von Webhooks:
- Statische Webhooks: Endpunkte auf Projektebene, die mit der Gemini WebhookService API konfiguriert werden. Gut für globale Integrationen (z.B. Benachrichtigung von Slack, Synchronisierung einer Datenbank usw.).
- Dynamische Webhooks: Überschreibungen auf Anfrageebene, bei denen eine Webhook-URL im Konfigurations-Payload eines bestimmten Jobaufrufs übergeben wird. Ideal, um bestimmte Jobs an dedizierte Endpunkte weiterzuleiten.
Statische Webhooks
Statische Webhooks werden für ein ganzes Projekt registriert und werden für jedes passende Ereignis ausgelöst.
Webhook erstellen
Sie können Endpunkte mit dem SDK oder der REST API erstellen.
WICHTIG: Wenn Sie einen Webhook erstellen, gibt die API nur einmal ein Signaturgeheimnis zurück. Sie müssen diesen Schlüssel sicher speichern, z.B. in Ihren Umgebungsvariablen, um Signaturen später zu überprüfen. Wenn Sie den Signaturschlüssel verlieren, müssen Sie ihn rotieren.
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"]
}'
Weitere Informationen zum Einrichten Ihres Servers für den Empfang von Daten finden Sie im Abschnitt Webhook-Anfragen verarbeiten.
Webhook abrufen
Rufen Sie Details zu einem bestimmten Webhook anhand seines Ressourcennamens ab.
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"
Webhooks auflisten
Listet alle konfigurierten Webhooks für das aktuelle Projekt mit optionaler Paginierung auf.
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"
Webhook aktualisieren
Aktualisieren Sie die Eigenschaften eines vorhandenen Webhooks, z. B. den Anzeigenamen, den Ziel-URI oder die abonnierten Ereignisse.
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"]
}'
Webhook löschen
Webhook-Endpunkt aus dem Projekt entfernen Dadurch werden keine zukünftigen Ereignisse mehr an diesen Endpunkt gesendet.
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"
Signatur-Secret rotieren
Signatur-Secret für einen Webhook rotieren Sie können konfigurieren, ob zuvor aktive Secrets sofort oder nach einem Kulanzzeitraum von 24 Stunden widerrufen werden.
WICHTIG: Das neue Signierungsgeheimnis wird nur einmal bei der Rotation zurückgegeben. Speichern Sie sie sicher, bevor Sie Ihre Bestätigungslogik aktualisieren.
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"
}'
Webhook-Anfragen auf einem Server verarbeiten
Wenn ein Ereignis eintritt, für das Sie sich registriert haben, empfängt Ihre Webhook-URL eine HTTP-POST-Anfrage. Ihr Endpunkt muss innerhalb weniger Sekunden mit einem 2xx-Statuscode antworten, um einen Wiederholungsversuch zu vermeiden. Damit die Zustellung gewährleistet ist, werden fehlgeschlagene Anfragen an die Gemini API automatisch 24 Stunden lang mit exponentiellem Backoff wiederholt.
Gemini folgt bei Sicherheitsheadern strikt der Spezifikation für Standard-Webhooks. Prüfen Sie die Nutzlast auf Ihrem Server mithilfe der Signaturen der signierten Header und Ihres gespeicherten statischen Signierungsgeheimnisses. Informationen zur Nutzlast finden Sie im Abschnitt Webhook-Umschlag.
Hier ist ein Beispiel für die Verwendung von Flask für den HTTP-Listener:
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");
});
Dynamische Webhooks
Mit dynamischen Webhooks können Sie einen Webhook-Endpunkt an eine bestimmte Anfragekonfiguration binden. Das ist ideal für Agent-Orchestration-Warteschlangen. Dynamische Webhooks nutzen asymmetrische JWKS-Signaturen mit öffentlichem Schlüssel anstelle von symmetrischen Secrets.
Dynamische Anfrage senden
Fügen Sie ein webhook_config hinzu, wenn Sie einen asynchronen Job auslösen (z.B. beim Erstellen eines Batches).
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"}
}
}
}'
Dynamische Signaturen (JWKS) überprüfen
Dynamische Webhook-Anfragen geben eine JWT-Signatur (JSON Web Token) aus. Ihr Listener muss die Signatur extrahieren und mit den öffentlichen Zertifikatendpunkten von Google überprüfen.
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-Umschlag
Um Bandbreitenüberlastungen zu vermeiden, verwenden Gemini-Webhooks ein Modell mit geringer Nutzlast, um Daten zu übertragen. Bei der Übermittlung wird ein Snapshot mit Statusdetails und Verweisen auf Ergebnisse gesendet, nicht die Rohausgabedatei selbst.
Hier ist ein Beispiel für das Nutzlastformat:
{
"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
}
}
Referenz zum Ereigniskatalog
Die folgenden Ereignisse werden für unterstützende Jobs ausgelöst:
| Ereignistyp | Trigger | Nutzlast-Element (data) |
|---|---|---|
batch.succeeded |
Die Verarbeitung wurde erfolgreich abgeschlossen. | id, output_file_uri |
batch.cancelled |
Nutzer hat Anfrage abgebrochen | id |
batch.expired |
Der Batch wurde nicht innerhalb von 24 Stunden verarbeitet (abgeschlossen) | id |
batch.failed |
Batchjob fehlgeschlagen (System- oder Validierungsfehler). | id, error_code, error_message |
interaction.requires_action |
Funktionsaufruf, Nutzer muss etwas tun | id |
interaction.completed |
LRO in Interactions API erfolgreich | id |
interaction.failed |
LRO in der Interactions API fehlgeschlagen (System- oder Validierungsfehler). | id, error_code, error_message |
interaction.cancelled |
LRO in Interactions API abgebrochen | id |
video.generated |
Die LRO für die Videogenerierung wurde abgeschlossen. | file_id, video_uri |
Best Practices
So sorgen Sie für einen zuverlässigen, skalierbaren Betrieb:
- Strenge Replay-Schutzprüfung: Alle Anfragen enthalten einen
webhook-timestamp-Header. Validieren Sie diesen Zeitstempel immer auf der Ebene Ihrer Serverkonfiguration, um Nutzlasten abzulehnen, die älter als 5 Minuten sind (um Replay-Angriffe zu verhindern). - Asynchron verarbeiten: Reagiere bei Erkennung einer gültigen Signatur sofort mit
2xx OKund stelle Parsing-Vorgänge intern in die Warteschlange. Längere Wartezeiten des Zuhörers lösen einen neuen Zustellversuch aus. - Deduplizierung: Standard-Webhooks werden „mindestens einmal“ gesendet. Verwenden Sie den einheitlichen
webhook-id-Header, um potenzielle Duplikate bei Flows mit höherer Überlastung zu verarbeiten.
Nächste Schritte
- Batch API: Nutzen Sie Webhooks, um Endpunkte mit hohem Volumen zu automatisieren.