Webhook

I webhook consentono all'API Gemini di inviare notifiche in tempo reale al tuo server al termine delle operazioni asincrone o a lunga esecuzione (LRO). In questo modo, non è più necessario eseguire il polling dell'API per gli aggiornamenti di stato, riducendo la latenza e il sovraccarico.

I webhook sono disponibili per operazioni come job batch, interazioni e generazione di video.

Come funziona

Anziché eseguire ripetutamente il polling di GET /operations per verificare se un job è terminato, puoi configurare i webhook dell'API Gemini in modo che inviino una richiesta HTTP POST al tuo URL listener immediatamente dopo l'attivazione di un evento.

L'API Gemini supporta due modi per configurare i webhook:

  • Webhook statici: endpoint a livello di progetto configurati con l'API Gemini WebhookService. Ideali per le integrazioni globali (ad es. invio di notifiche a Slack, sincronizzazione di un database e così via).
  • Webhook dinamici: sostituzioni a livello di richiesta che passano un URL webhook nel payload di configurazione di una chiamata di job specifica. Ideali per il routing di job specifici a endpoint dedicati.

Webhook statici

I webhook statici vengono registrati per un intero progetto e vengono attivati per qualsiasi evento corrispondente.

Crea un webhook

Puoi creare endpoint utilizzando l'SDK o l'API REST.

IMPORTANTE: quando crei un webhook, l'API restituisce un secret di firma una sola volta. Devi archiviarlo in modo sicuro (ad es. nelle variabili di ambiente) per verificare le firme in un secondo momento. Se perdi il secret di firma, dovrai ruotarlo.

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

Per informazioni dettagliate sulla configurazione del server per la ricezione dei dati, consulta la sezione Gestire le richieste webhook.

Ottieni un webhook

Recupera i dettagli di un webhook specifico in base al relativo nome risorsa.

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"

Elenca i webhook

Elenca tutti i webhook configurati per il progetto corrente, con la paginazione facoltativa.

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"

Aggiorna un webhook

Aggiorna le proprietà di un webhook esistente, ad esempio il nome visualizzato, l'URI di destinazione o gli eventi a cui hai eseguito la sottoscrizione.

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

Elimina un webhook

Rimuovi un endpoint webhook dal progetto. In questo modo, le consegne di eventi futuri a questo endpoint verranno interrotte.

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"

Ruota un secret di firma

Ruota il secret di firma per un webhook. Puoi configurare se i secret attivi in precedenza vengono revocati immediatamente o dopo un periodo di tolleranza di 24 ore.

IMPORTANTE: il nuovo secret di firma viene restituito una sola volta al momento della rotazione Archivialo in modo sicuro prima di aggiornare la logica di verifica.

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

Gestisci le richieste webhook su un server

Quando si verifica un evento a cui hai eseguito la sottoscrizione, il tuo URL webhook riceverà una richiesta HTTP POST. L'endpoint deve rispondere con un codice di stato 2xx entro pochi secondi per evitare un nuovo tentativo. Per garantire la consegna, l'API Gemini ritenta automaticamente le richieste non riuscite per 24 ore utilizzando il backoff esponenziale.

Gemini rispetta rigorosamente la specifica dei webhook standard per le intestazioni di sicurezza. Verifica il payload sul server utilizzando le firme delle intestazioni firmate e il secret di firma statico archiviato. Per informazioni sul payload, consulta la sezione Busta del webhook.

Ecco un esempio di utilizzo di Flask per il 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");
});

Webhook dinamici

I webhook dinamici ti consentono di associare un endpoint webhook a una configurazione di richiesta specifica, ideale per le code di orchestrazione degli agenti. I webhook dinamici utilizzano firme JWKS con chiave pubblica asimmetrica anziché secret simmetrici.

Invia una richiesta dinamica

Aggiungi un webhook_config quando attivi un job asincrono (ad es. la creazione di un 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"}
      }
    }
  }'

Verifica le firme dinamiche (JWKS)

Le richieste di webhook dinamici emettono una firma JWT (JSON Web Token). Il listener deve estrarre la firma e verificarla utilizzando gli endpoint dei certificati pubblici di 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" });
    }
  );
});

Busta del webhook

Per evitare la congestione della larghezza di banda, i webhook di Gemini utilizzano un modello di payload leggero per la distribuzione dei dati. Le distribuzioni inviano uno snapshot contenente i dettagli dello stato e i puntatori ai risultati, anziché il file di output non elaborato.

Ecco un esempio di formato del 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
  }
}

Riferimento al catalogo degli eventi

I seguenti eventi vengono attivati per i job supportati:

Tipo di evento Trigger Elemento payload (data)
batch.succeeded Elaborazione completata correttamente. id, output_file_uri
batch.cancelled Richiesta annullata dall'utente id
batch.expired Il batch non è stato elaborato (completato) nell'arco di 24 ore id
batch.failed Esecuzione del job batch non riuscita (errore di sistema o di convalida). id, error_code, error_message
interaction.requires_action Chiamata di funzione, l'utente deve eseguire un'azione id
interaction.completed L'operazione a lunga esecuzione nell'API Interactions è riuscita id
interaction.failed L'operazione a lunga esecuzione nell'API Interactions non è riuscita (errore di sistema o di convalida). id, error_code, error_message
interaction.cancelled L'operazione a lunga esecuzione nell'API Interactions è stata annullata id
video.generated L'operazione a lunga esecuzione di generazione video è stata completata. file_id, video_uri

Best practice

Per garantire un'operazione affidabile e scalabile:

  • Controllo rigoroso della protezione dalla riproduzione: tutte le richieste includono un'webhook-timestamp intestazione. Convalida sempre questo timestamp nel livello di configurazione del server per rifiutare i payload più vecchi di 5 minuti (per mitigare gli attacchi di riproduzione).
  • Elabora in modo asincrono: rispondi con 2xx OK immediatamente dopo il rilevamento di una firma valida e metti in coda internamente le operazioni di analisi. I tempi di attesa prolungati del listener attiveranno un ciclo di nuovi tentativi di consegna.
  • Gestione della deduplicazione: i webhook standard forniscono "almeno una volta". Utilizza l'intestazione webhook-id coerente per gestire i potenziali duplicati nei flussi di congestione più elevati.

Passaggi successivi

  • API Batch: utilizza i webhook per automatizzare gli endpoint a volume elevato.