Вебхуки

Веб-хуки позволяют API Gemini отправлять на ваш сервер уведомления в режиме реального времени о завершении асинхронных операций или длительных операций (LRO). Это избавляет от необходимости опрашивать API для получения обновлений статуса, снижая задержку и накладные расходы.

Веб-хуки доступны для таких операций, как пакетная обработка заданий, взаимодействие с пользователями и генерация видео .

Как это работает

Вместо того чтобы многократно опрашивать GET /operations для проверки завершения задания, вы можете настроить веб-перехватчики API Gemini таким образом, чтобы они отправляли HTTP POST-запрос на ваш URL-адрес прослушивателя сразу после срабатывания события.

API Gemini поддерживает два способа настройки веб-хуков:

  • Статические веб-хуки : конечные точки на уровне проекта, настраиваемые с помощью API Gemini WebhookService . Подходят для глобальной интеграции (например, уведомления 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();

ОТДЫХ

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();

ОТДЫХ

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();

ОТДЫХ

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();

ОТДЫХ

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();

ОТДЫХ

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();

ОТДЫХ

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 в течение нескольких секунд, чтобы избежать повторной попытки. Для обеспечения доставки API Gemini автоматически повторяет неудачные запросы в течение 24 часов, используя экспоненциальную задержку.

Gemini строго следует спецификации Standard Webhooks для заголовков безопасности. Проверьте полезную нагрузку на вашем сервере, используя подписанные подписи заголовков и сохраненный статический секретный ключ подписи. Информацию о полезной нагрузке см. в разделе «Конверт веб-перехватчика» .

Вот пример использования 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") 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 при запуске асинхронного задания (например, при создании пакетного задания).

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"}
      },
    },
  });
}

ОТДЫХ

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). Ваш обработчик должен извлечь эту подпись и проверить её, используя общедоступные конечные точки сертификатов 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 в API взаимодействий успешно реализован id
interaction.failed Ошибка LRO в API взаимодействий (системная ошибка или ошибка проверки). id , error_code , error_message
interaction.cancelled LRO во взаимодействии API отменен id
video.generated Завершено создание видео с помощью LRO. file_id , video_uri

Передовые методы

Для обеспечения надежной и масштабируемой работы:

  • Строгая проверка защиты от повторного воспроизведения : все запросы содержат заголовок webhook-timestamp . Всегда проверяйте эту метку времени на уровне конфигурации вашего сервера, чтобы отклонять полезные данные старше 5 минут (для снижения риска атак повторного воспроизведения).
  • Обработка асинхронная : немедленно отправляйте ответ 2xx OK после обнаружения действительной подписи и ставьте операции анализа в очередь внутри системы. Длительное время ожидания ответа от слушателя приведет к циклу повторной попытки доставки.
  • Обработка дедупликаций : Стандартные веб-хуки передают сообщение "Как минимум один раз". Используйте согласованный заголовок webhook-id для обработки потенциальных дубликатов в потоках с высокой загрузкой.

Что дальше?

  • Пакетный API : Используйте веб-хуки для автоматизации обработки больших объемов запросов.