{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "FQRDCZcjtuGS" }, "source": [ "Copyright 2026 Google LLC." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "id": "AgLUtgfYtpJ8" }, "outputs": [], "source": [ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", "#\n", "# https://www.apache.org/licenses/LICENSE-2.0\n", "#\n", "# Unless required by applicable law or agreed to in writing, software\n", "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", "# See the License for the specific language governing permissions and\n", "# limitations under the License." ] }, { "cell_type": "markdown", "metadata": { "id": "OzS4i5rqt1t7" }, "source": [ "# Webhooks\n", "\n", "\n", "\n", "Preview: Webhooks support in the Gemini API is currently in preview.\n", "\n", "Webhooks allow the Gemini API to push real-time notifications to your server\n", "when asynchronous or Long-Running Operations (LROs) complete. This replaces the\n", "need to poll the API for status updates, reducing latency and overhead.\n", "\n", "Webhooks are available for operations like [Batch](https://ai.google.dev/gemini-api/docs/batch) jobs,\n", "[Interactions](https://ai.google.dev/gemini-api/docs/interactions) and [video generation](https://ai.google.dev/gemini-api/docs/video).\n", "\n", "## How it works\n", "\n", "Instead of polling `GET /operations` repeatedly to check if a job is finished,\n", "you can configure Gemini API Webhooks to send an HTTP POST request to your\n", "listener URL immediately upon an event trigger.\n", "\n", "The Gemini API supports two ways to configure webhooks:\n", "\n", "* **Static webhooks**: Project-level endpoints. Good for global integrations (e.g., notifying Slack, syncing a\n", "database, etc.).\n", "* **Dynamic webhooks**: Request-level overrides passing a\n", "webhook URL in the configuration payload of a specific jobs call. Ideal for\n", "routing specific jobs to dedicated endpoints.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "gPGKO0mJ0Qgw" }, "source": [ "# Setup\n", "\n", "### Install SDK\n", "\n", "Install the SDK from [PyPI](https://github.com/googleapis/python-genai)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "kWAqRRUy0bX4" }, "outputs": [], "source": [ "%pip install -U -q google-genai # If you see an error related to google-auth version mismatch, re-run the cell" ] }, { "cell_type": "markdown", "metadata": { "id": "wM9RMu5u0j0c" }, "source": [ "### Setup your API key\n", "\n", "To run the following cell, your API key must be stored in a Colab Secret named `GEMINI_API_KEY`. If you don't already have an API key, or you're not sure how to create a Colab Secret, see [Authentication ![image](https://storage.googleapis.com/generativeai-downloads/images/colab_icon16.png)](../quickstarts/Authentication.ipynb) for a walkthrough." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "ouhCFDWd0krv" }, "outputs": [], "source": [ "from google.colab import userdata\n", "\n", "GEMINI_API_KEY = userdata.get('GEMINI_API_KEY')" ] }, { "cell_type": "markdown", "metadata": { "id": "fc43_qJH0uhn" }, "source": [ "### Initialize SDK client\n", "\n", "Initialize a client with your API key:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "N6iv8_Cl0yNl" }, "outputs": [], "source": [ "import os\n", "from google import genai\n", "from google.genai import errors, types\n", "\n", "client = genai.Client(api_key=GEMINI_API_KEY)" ] }, { "cell_type": "markdown", "metadata": { "id": "mm-1oBdiNrpO" }, "source": [ "## Step 1: Create a webhook endpoint\n", "\n", "This example uses a Google Cloud Run function, which allow you to easily deploy a serverless function you can use as our webhook endpoint.\n", "\n", "1. Go to https://console.cloud.google.com/run/\n", "2. Click NodeJS under Write a function\n", "3. Enter a name such as \"gemini-webhook-receiver\" and select \"Allow public access\" under Authentication\n", "4. Click \"Deploy\". If this is the first time you're deploying a Cloud Run function, you may need to enable APIs such as Cloud Build as prompted.\n", "\n", "(Their management UI at https://console.cloud.google.com/run/ has a log observation page, you can see the requests there)\n", "\n", "5. **Implement webhook signature verification**: In the Source tab, update the `package.json` dependencies with\n", "\n", "```jsdo\n", "\"dependencies\": {\n", "\t\t\"standardwebhooks\": \"^1.0.0\"\n", "\t}\n", "```\n", "\n", "Update the Function entry point to `geminiWebhook` \n", "\n", "and replace the code in `/src/index.js` with the following code, then click \"Save abd redeploy\"\n", "\n", "```javascript\n", "const functions = require('@google-cloud/functions-framework');\n", "const { Webhook } = require('standardwebhooks');\n", "\n", "/**\n", " * Google Cloud Function to handle Gemini API Webhooks\n", " */\n", "functions.http('geminiWebhook', async (req, res) => {\n", " // Respond with a 200 for non-POST requests to indicate that the function is running\n", " if (req.method !== 'POST') {\n", " return res.status(200).send('Worker is running');\n", " }\n", "\n", " // Print headers and body to stdout\n", " console.log('--- Webhook Headers ---');\n", " console.log(JSON.stringify(req.headers, null, 2));\n", " console.log('--- Webhook Body ---');\n", " console.log(JSON.stringify(req.body, null, 2));\n", "\n", " const payload = JSON.stringify(req.body);\n", " \n", " // Extract specific headers for Standard Webhooks verification\n", " // This avoids issues with extra headers added by Cloud Run/Functions proxy\n", " const headers = {\n", " 'webhook-id': req.headers['webhook-id'],\n", " 'webhook-signature': req.headers['webhook-signature'],\n", " 'webhook-timestamp': req.headers['webhook-timestamp'],\n", " };\n", "\n", " const secret = process.env.WEBHOOK_SIGNING_SECRET;\n", "\n", " try {\n", " const wh = new Webhook(secret);\n", " // Verify the webhook signature using only the relevant headers\n", " const event = wh.verify(payload, headers);\n", " \n", " console.log(`Event type: ${event.type}, data: ${JSON.stringify(event.data)}`);\n", "\n", " if (event.type === 'batch.succeeded') {\n", " console.log(`Batch completed! ID: ${event.data.id}`);\n", " if (event.data.output_file_uri) {\n", " console.log(`Batch file: ${event.data.output_file_uri}`);\n", " }\n", " } else if (event.type === 'video.generated') {\n", " console.log(`Video generated! URI: ${event.data.video_uri}`);\n", " }\n", "\n", " res.status(200).json({ status: 'received' });\n", " } catch (err) {\n", " console.error('Webhook verification failed:', err.message);\n", " res.status(400).json({ error: 'Signature invalid' });\n", " }\n", "});\n", "```\n", "\n", " When an event happens that you're subscribed to, your webhook URL will receive an HTTP POST request. Your endpoint must respond with a 2xx status code within a few seconds to avoid a retry. To ensure delivery, the Gemini API automatically retries failed requests for 24 hours using exponential backoff.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "NwSXwFeLTJka" }, "source": [ "TODO: Change this to use your own webhook endpoint!" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "kwbQ7BIfNxEb" }, "outputs": [], "source": [ "# TODO: Copy and paste the URL for the function you just deployed above\n", "WEBHOOK_ENDPOINT=\"https://your-app.run.app\" # @param {type:\"string\"}" ] }, { "cell_type": "markdown", "metadata": { "id": "GgT7OnPGRxo5" }, "source": [ "Check that the endpoint is working. This example endpoint simply returns a text for a GET request." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "QxvHLqFzRDoh" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Worker is running!\n" ] } ], "source": [ "import requests\n", "\n", "r = requests.get(WEBHOOK_ENDPOINT)\n", "print(r.text)" ] }, { "cell_type": "markdown", "metadata": { "id": "cE_mOixMQbu5" }, "source": [ "## Step 2: Create a webhook and add the Secret to Cloud Run function\n", "\n", "**IMPORTANT**: When creating a webhook, the API returns a **signing secret**\n", "**only once**. Take the secret below and add it to your Cloud Run function.\n", "\n", "Steps\n", "- On Google Cloud Console for your function, Click \"Edit and deploy new revision\"\n", "- Then, in the \"Containers\" > \"Variables and Secrets\" tab, click \"Add variable\"\n", "- Then, enter the secret \"WEBHOOK_SIGNING_SECRET\" with the value as follows.\n", "- Then click Deploy." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "2pHqV-ugQkDE" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Created webhook: video_generation_webhook\n" ] } ], "source": [ "webhook = client.webhooks.create(\n", " subscribed_events=[\"video.generated\"],\n", " name=\"video_generation_webhook\",\n", " uri=WEBHOOK_ENDPOINT,\n", ")\n", "\n", "# Store webhook.signing_secret securely (e.g., in your env vars)\n", "os.environ[\"WEBHOOK_SIGNING_SECRET\"] = webhook.new_signing_secret\n", "\n", "# print(f\"WEBHOOK_SIGNING_SECRET={webhook.new_signing_secret}\")\n", "# # Or write it to a file:\n", "# with open(\".env\", \"a\") as f:\n", "# f.write(f\"WEBHOOK_SIGNING_SECRET={webhook.new_signing_secret}\\n\")\n", "\n", "print(f\"Created webhook: {webhook.name}\")\n" ] }, { "cell_type": "markdown", "metadata": { "id": "brQO0mgdAZE7" }, "source": [ "### List\n", "\n", "List all configured webhooks for the current project, with optional pagination." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "f45px9hnANx_" }, "outputs": [ { "data": { "text/plain": [ "WebhookListResponse(next_page_token=None, webhooks=[Webhook(subscribed_events=['video.generated'], uri='https://read-post.pythonengineerman.workers.dev/', id='k0h9q38f6ejj1g9j7cgcdsauadmbewegl79f', create_time=None, name='video_generation_webhook', new_signing_secret=None, signing_secrets=[SigningSecret(expire_time=None, truncated_secret='whsec_...3frh')], state='enabled', update_time=None)])" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "client.webhooks.list()" ] }, { "cell_type": "markdown", "metadata": { "id": "whSVIqtSAbrx" }, "source": [ "### Get/Ping/Delete/Update" ] }, { "cell_type": "markdown", "metadata": { "id": "hzoxlwQ3Df5P" }, "source": [ "Ping a webhook" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "jJH8pXdQR_OW" }, "outputs": [ { "data": { "text/plain": [ "WebhookPingResponse()" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "client.webhooks.ping(id=webhook.id)" ] }, { "cell_type": "markdown", "metadata": { "id": "Jugm0AuLDeGM" }, "source": [ "Get a webhook" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Su_7yukaB8a6" }, "outputs": [], "source": [ "wh = client.webhooks.get(id=webhook.id)\n", "wh" ] }, { "cell_type": "markdown", "metadata": { "id": "TIsjm7TnDbEP" }, "source": [ "This would delete the webhook. Set `delete=True` to run it:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "UTc2Oj7vAd9s" }, "outputs": [], "source": [ "delete = False\n", "if delete:\n", " client.webhooks.delete(id=webhook.id)" ] }, { "cell_type": "markdown", "metadata": { "id": "7kMferMcTcBB" }, "source": [ "This is how you can update a webhook. Set `update=True` to run it:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "xKm18EH3zixU" }, "outputs": [], "source": [ "update = False\n", "if update:\n", " wh = client.webhooks.update(\n", " id=webhook.id,\n", " subscribed_events=[\"video.generated\", \"batch.succeeded\"],\n", " name=\"video_and_batch_webhook\",\n", " )" ] }, { "cell_type": "markdown", "metadata": { "id": "fOLlL0rUKjeR" }, "source": [ "## Step 4: Send a Gemini API request\n", "\n", "Send a request for the long running operation your webhook is subscribed to." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "NMMztX_qKqJM" }, "outputs": [], "source": [ "prompt = \"\"\"A stunning drone view of the Grand Canyon during a flamboyant sunset that highlights the canyon's colors. The drone slowly flies towards the sun then accelerates, dives and flies inside the canyon.\"\"\"\n", "\n", "operation = client.models.generate_videos(\n", " model=\"veo-3.1-generate-preview\",\n", " prompt=prompt,\n", ")" ] }, { "cell_type": "markdown", "metadata": { "id": "dL5TisQuVIAO" }, "source": [ "Now, instead of polling GET /operations, your webhook should receive an HTTP POST request once the video generation has finished." ] }, { "cell_type": "markdown", "metadata": { "id": "2nUNPecNSHMJ" }, "source": [ "## Step 5: Check the worker logs\n", "\n", "Check the **Observability > Logs** tab in your Cloud Run Dashboard \n", "\n", "It should have logs that show the Webhook payload and the output of the function, in this case it should display the URL of the generated video." ] }, { "cell_type": "markdown", "metadata": { "id": "i_8Cq9eK6FSY" }, "source": [ "## Dynamic webhook config" ] }, { "cell_type": "markdown", "metadata": { "id": "hK8kbtnoWnpt" }, "source": [ "You can also use dynamic webhook configuration, but for this, you need to update your webhook verification.\n", "\n", "See the docs for implementation details:\n", "\n", "- https://ai.google.dev/gemini-api/docs/webhooks#dynamic-webhooks\n", "- https://ai.google.dev/gemini-api/docs/webhooks#verify-dynamic-signatures\n", "\n", "\n", "## Next Steps:\n", "\n", "- **Webhooks documentation**: Read the full [Webhooks guide](https://ai.google.dev/gemini-api/docs/webhooks) for advanced configuration options, supported event types, and best practices.\n", "- **Batch API**: Webhooks pair well with the [Batch API](./Batch_mode.ipynb) to process large volumes of requests and get notified when they complete.\n", "- **Video generation**: Use webhooks with [Veo](./Get_started_Veo.ipynb) to get notified when your generated videos are ready.\n", "- **Cloud Run functions**: Learn more about [Cloud Run functions](https://cloud.google.com/functions).\n", "- **Standard Webhooks**: The Gemini API uses the [Standard Webhooks](https://www.standardwebhooks.com/) specification for signature verification, ensuring secure and interoperable webhook delivery.\n" ] } ], "metadata": { "colab": { "name": "Webhooks.ipynb", "toc_visible": true }, "kernelspec": { "display_name": "Python 3", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 0 }