Webhooki

Webhooki umożliwiają interfejsowi Gemini API wysyłanie powiadomień w czasie rzeczywistym na Twój serwer, gdy zakończą się operacje asynchroniczne lub długotrwałe (LRO). Zastępuje to konieczność odpytywania interfejsu API o aktualizacje stanu, co zmniejsza opóźnienia i obciążenie.

Webhooki są dostępne w przypadku operacji takich jak zadania wsadowe, interakcjegenerowanie filmów.

Jak to działa

Zamiast wielokrotnie odpytywać GET /operations, czy zadanie zostało zakończone, możesz skonfigurować webhooki Gemini API tak, aby po wywołaniu zdarzenia natychmiast wysyłały żądanie HTTP POST na adres URL odbiornika.

Interfejs Gemini API obsługuje 2 sposoby konfigurowania webhooków:

  • Statyczne webhooki: punkty końcowe na poziomie projektu skonfigurowane za pomocą interfejsu WebhookService API Gemini. Ta opcja sprawdza się w przypadku integracji globalnych (np. powiadamiania Slacka, synchronizowania bazy danych itp.).
  • Dynamiczne webhooki: zastąpienia na poziomie żądania przekazujące adres URL webhooka w ładunku konfiguracyjnym konkretnego wywołania interfejsu Jobs API. Idealne rozwiązanie do kierowania określonych zadań do dedykowanych punktów końcowych.

Statyczne webhooki

Statyczne webhooki są rejestrowane dla całego projektu i wyzwalane w przypadku każdego pasującego zdarzenia.

Tworzenie webhooka

Punkty końcowe możesz tworzyć za pomocą pakietu SDK lub interfejsu API REST.

WAŻNE: podczas tworzenia webhooka interfejs API zwraca tajny klucz podpisywania tylko raz. Musisz bezpiecznie przechowywać ten klucz (np. w zmiennych środowiskowych), aby później weryfikować podpisy. Jeśli utracisz klucz tajny podpisywania, musisz go zrotować.

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"]
  }'

Więcej informacji o konfigurowaniu serwera do odbierania danych znajdziesz w sekcji Obsługa żądań webhooka.

Pobieranie webhooka

Pobieranie szczegółowych informacji o konkretnym webhooku na podstawie jego nazwy zasobu.

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"

Wyświetlanie listy webhooków

Wyświetla listę wszystkich skonfigurowanych webhooków w bieżącym projekcie z opcjonalnym podziałem na strony.

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"

Aktualizowanie webhooka

Aktualizowanie właściwości istniejącego webhooka, takich jak wyświetlana nazwa, docelowy URI lub subskrybowane zdarzenia.

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"]
  }'

Usuwanie webhooka

Usuń punkt końcowy webhooka z projektu. Spowoduje to zatrzymanie dostarczania przyszłych zdarzeń do tego punktu końcowego.

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"

Wykonaj rotację obiektu tajnego podpisywania

Zmień obiekt tajny podpisywania webhooka. Możesz skonfigurować, czy wcześniej aktywne klucze tajne mają być unieważniane natychmiast, czy po 24-godzinnym okresie prolongaty.

WAŻNE: nowy klucz tajny podpisywania jest zwracany tylko raz w momencie rotacji. Zanim zaktualizujesz logikę weryfikacji, zapisz go w bezpiecznym miejscu.

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

Obsługa żądań webhooka na serwerze

Gdy nastąpi zdarzenie, na które masz subskrypcję, Twój adres URL webhooka otrzyma żądanie HTTP POST. Aby uniknąć ponownej próby, punkt końcowy musi odpowiedzieć kodem stanu 2xx w ciągu kilku sekund. Aby zapewnić dostarczenie, interfejs Gemini API automatycznie ponawia nieudane żądania przez 24 godziny, stosując algorytm wzrastającego czasu do ponowienia.

Gemini ściśle przestrzega specyfikacji Standard Webhooks w przypadku nagłówków bezpieczeństwa. Sprawdź ładunek na serwerze, używając podpisów w podpisanym nagłówku i zapisanego statycznego tajnego klucza podpisywania. Informacje o ładunku znajdziesz w sekcji Koperta webhooka.

Oto przykład użycia platformy Flask w przypadku odbiornika 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");
});

Dynamiczne webhooki

Dynamiczne wywołania zwrotne umożliwiają powiązanie punktu końcowego wywołania zwrotnego z konkretną konfiguracją żądania, co jest idealne w przypadku kolejek orkiestracji agentów. Dynamiczne webhooki wykorzystują asymetryczne podpisy JWKS klucza publicznego zamiast symetrycznych kluczy tajnych.

Przesyłanie dynamicznej prośby

Dodaj webhook_config podczas wywoływania zadania asynchronicznego (np. tworzenia 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: $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"}
      }
    }
  }'

Weryfikowanie podpisów dynamicznych (JWKS)

Żądania dynamicznych webhooków emitują podpis tokena sieciowego JSON (JWT). Odbiornik musi wyodrębnić podpis i zweryfikować go za pomocą punktów końcowych certyfikatu publicznego 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" });
    }
  );
});

Koperta webhooka

Aby uniknąć przeciążenia przepustowości, webhooki Gemini używają modelu cienki ładunek do dostarczania danych. Dostawy wysyłają migawkę zawierającą szczegóły stanu i wskaźniki wyników, a nie sam plik wyjściowy.

Oto przykładowy format ładunku:

{
  "type": "batch.succeeded",
  "version": "v1",
  "timestamp": "2026-01-22T12:00:00Z",
  "data": {
    "id": "batch_123456",
    "output_file_uri": "gs://my-bucket/results.jsonl"
  }
}

Informacje o katalogu zdarzeń

W przypadku zadań pomocniczych wywoływane są te zdarzenia:

Typ zdarzenia Wyzwalacz Element ładunku (data)
batch.succeeded Przetwarzanie zostało zakończone. id, output_file_uri
batch.cancelled Użytkownik anulował prośbę id
batch.expired Partia nie została przetworzona (zakończona) w ciągu 24 godzin id
batch.failed Zadanie wsadowe zakończyło się niepowodzeniem (błąd systemu lub weryfikacji). id, error_code, error_message
interaction.requires_action Wywołanie funkcji, użytkownik musi coś zrobić id
interaction.completed Długotrwała operacja w interfejsie API interakcji zakończyła się powodzeniem id
interaction.failed Nie udało się wykonać operacji LRO w interfejsie API interakcji (błąd systemu lub weryfikacji). id, error_code, error_message
interaction.cancelled Anulowanie długotrwałej operacji w interfejsie API interakcji id
video.generated Zakończono długotrwałą operację generowania filmu. id, output_file_uri, file_name

Sprawdzone metody

Aby zapewnić niezawodne i skalowalne działanie:

  • Ścisła ochrona przed powtórzeniem: wszystkie żądania zawierają webhook-timestampnagłówek. Zawsze sprawdzaj ten sygnaturę czasową w warstwie konfiguracji serwera, aby odrzucać ładunki starsze niż 5 minut (w celu ograniczenia ataków typu replay).
  • Przetwarzanie asynchroniczne: odpowiadaj za pomocą 2xx OK natychmiast po wykryciu prawidłowego podpisu i kolejkuj wewnętrznie operacje analizowania. Długi czas oczekiwania słuchacza spowoduje ponowienie próby dostarczenia.
  • Obsługa duplikatów: standardowe webhooki dostarczają dane „co najmniej raz”. Używaj spójnego nagłówka webhook-id, aby obsługiwać potencjalne duplikaty w przypadku większego natężenia ruchu.

Co dalej?

  • Batch API: Używaj webhooków do automatyzacji punktów końcowych o dużej liczbie wywołań.