Os webhooks permitem que a API Gemini envie notificações em tempo real para seu servidor quando operações assíncronas ou de longa duração (LROs) são concluídas. Isso substitui a necessidade de sondar a API para atualizações de status, reduzindo a latência e a sobrecarga.
Os webhooks estão disponíveis para operações como jobs em lote, interações e geração de vídeo.
Como funciona
Em vez de fazer polling de GET /operations repetidamente para verificar se um job foi concluído, você pode configurar webhooks da API Gemini para enviar uma solicitação HTTP POST ao URL do listener imediatamente após um gatilho de evento.
A API Gemini oferece duas maneiras de configurar webhooks:
- Webhooks estáticos: endpoints no nível do projeto configurados com a API WebhookService do Gemini. Ideal para integrações globais (por exemplo, notificar o Slack, sincronizar um banco de dados etc.).
- Webhooks dinâmicos: substituições no nível da solicitação que transmitem um URL do webhook no payload de configuração de uma chamada de jobs específica. Ideal para direcionar jobs específicos a endpoints dedicados.
Webhooks estáticos
Os webhooks estáticos são registrados para um projeto inteiro e são acionados para qualquer evento correspondente.
Criar um webhook
É possível criar endpoints usando o SDK ou a API REST.
IMPORTANTE: ao criar um webhook, a API retorna um segredo de assinatura apenas uma vez. Armazene isso com segurança (por exemplo, nas variáveis de ambiente) para verificar as assinaturas mais tarde. Se você perder a chave secreta de assinatura, será necessário fazer a rotação dela.
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"]
}'
Para detalhes sobre como configurar seu servidor para receber dados, consulte a seção Processar solicitações de webhook.
Receber um webhook
Recupera detalhes sobre um webhook específico pelo nome do recurso.
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"
Listar webhooks
Lista todos os webhooks configurados para o projeto atual, com paginação opcional.
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"
Atualizar um webhook
Atualize as propriedades de um webhook existente, como o nome de exibição, o URI de destino ou os eventos inscritos.
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"]
}'
Excluir um webhook
Remove um endpoint de webhook do projeto. Isso interrompe as entregas de eventos futuros para esse endpoint.
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"
Alternar um secret de assinatura
Alternar o secret de assinatura de um webhook. É possível configurar se os secrets ativos anteriormente serão revogados imediatamente ou após um período de carência de 24 horas.
IMPORTANTE: o novo segredo de assinatura é retornado apenas uma vez no momento da rotação. Armazene com segurança antes de atualizar a lógica de verificação.
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"
}'
Processar solicitações de webhook em um servidor
Quando um evento a que você se inscreveu acontece, o URL do webhook recebe uma solicitação HTTP POST. Seu endpoint precisa responder com um código de status 2xx em alguns segundos para evitar uma nova tentativa. Para garantir a entrega, a API Gemini repete automaticamente as solicitações com falha por 24 horas usando a espera exponencial.
O Gemini segue estritamente a especificação Webhooks padrão (em inglês) para cabeçalhos de segurança. Verifique o payload no seu servidor usando as assinaturas de cabeçalho assinado e o secret de assinatura estático armazenado. Consulte a seção Envelope do webhook para informações de payload.
Confira um exemplo usando Flask para o 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");
});
Webhooks dinâmicos
Os webhooks dinâmicos permitem vincular um endpoint de webhook a uma configuração de solicitação específica, ideal para filas de orquestração de agentes. Os webhooks dinâmicos usam assinaturas JWKS de chave pública assimétrica em vez de secrets simétricos.
Enviar uma solicitação dinâmica
Adicione um webhook_config ao acionar um job assíncrono (por exemplo, criar um
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"}
}
}
}'
Verificar assinaturas dinâmicas (JWKS)
As solicitações de webhook dinâmico emitem uma assinatura de JSON Web Token (JWT). Seu listener precisa extrair a assinatura e verificar usando os endpoints de certificado público do 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" });
}
);
});
Envelope do webhook
Para evitar congestionamento de largura de banda, os webhooks do Gemini usam um modelo de payload fino para entregar dados. As entregas enviam um snapshot com detalhes de status e indicadores para resultados, em vez do arquivo de saída bruto.
Confira um exemplo de formato de payload:
{
"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
}
}
Referência do catálogo de eventos
Os seguintes eventos são acionados para jobs de suporte:
| Tipo de evento | Gatilho | Item de payload (data) |
|---|---|---|
batch.succeeded |
O processamento foi concluído. | id, output_file_uri |
batch.cancelled |
Solicitação cancelada pelo usuário | id |
batch.expired |
O lote não foi processado (concluído) em um período de 24 horas | id |
batch.failed |
Falha no job em lote (erro de sistema ou validação). | id, error_code, error_message |
interaction.requires_action |
Chamada de função, o usuário precisa fazer algo | id |
interaction.completed |
LRO na API de interações concluída | id |
interaction.failed |
Falha na LRO na API de interações (erro de sistema ou validação). | id, error_code, error_message |
interaction.cancelled |
LRO na API de interações cancelada | id |
video.generated |
A LRO de geração de vídeo foi concluída. | file_id, video_uri |
Práticas recomendadas
Para garantir uma operação confiável e escalonável:
- Verificação de proteção contra repetição estrita: todas as solicitações têm um cabeçalho
webhook-timestamp. Sempre valide esse carimbo de data/hora na camada de configuração do servidor para rejeitar payloads com mais de 5 minutos (para reduzir ataques de repetição). - Processamento assíncrono: responda com
2xx OKimediatamente após a detecção de uma assinatura válida e coloque as operações de análise na fila internamente. Tempos de espera prolongados do listener vão acionar um ciclo de novas tentativas de entrega. - Processamento de remoção de duplicidade: os webhooks padrão fazem a entrega "pelo menos uma vez". Use o cabeçalho
webhook-idconsistente para lidar com possíveis duplicados em fluxos de congestionamento mais altos.
A seguir
- API Batch: use webhooks para automatizar endpoints de alto volume.