Event-Driven Compute with Cloud Functions: Triggers, Eventarc, and Real Examples

Event-driven compute means your code runs in response to things that happen, not on a schedule or inside a long-running server. A file lands in a bucket. A Pub/Sub message arrives. A Firestore document is created. An audit log records a resource change. Cloud Functions 2nd gen handles all of these through Eventarc, GCP’s event routing layer. This page explains how the pattern works, covers the four main trigger families, and shows real deployment examples you can adapt directly.

If you are new to Cloud Functions entirely, read the Cloud Functions overview first. That page covers runtimes, HTTP triggers, and the 1st gen vs 2nd gen comparison.

Simple explanation

Think of a smoke alarm. It doesn’t hire a security guard to walk the building every ten minutes checking for fire. Instead, a sensor waits quietly, costs nothing while idle, and fires an action the instant smoke is detected. Event-driven compute works the same way: your function does not run between events. When the event arrives, GCP starts the function, runs it, and stops it. You pay only for the time it actually runs.

A trigger is the rule that says what to watch for: “When a file is uploaded to this bucket, run this function.” Or: “When a message arrives on this Pub/Sub topic, run this function.” The trigger connects the event source to your code.

Eventarc is the GCP service that routes events from over 90 GCP services to your function. You don’t build integrations directly against each source service. Eventarc listens to them and delivers structured payloads to your function in a format called CloudEvents. Cloud Functions 2nd gen uses Eventarc under the hood for all trigger types, including the simpler Storage and Pub/Sub ones.

The key difference from polling or background workers: there is no process sitting idle between events, consuming CPU and memory while waiting for something to happen. The function only exists when needed, which makes this pattern cheap at low volumes and easy to scale at high ones.

How it works

Here is the generic flow from event source to function execution:

  1. An event occurs in a GCP service: a file upload completes in Cloud Storage, a message is published to a Pub/Sub topic, a Firestore document is written.

  2. Eventarc wraps it in a CloudEvent. Think of CloudEvents like a postal envelope: it has a standard format with a type, source, event ID, and timestamp on the outside, and the event-specific payload inside. Your function always unpacks the same envelope shape, regardless of which service sent it.

  3. Eventarc delivers the CloudEvent to your Cloud Function via an HTTPS POST request. If there is no running instance, GCP starts one cold.

  4. Your function handler runs, reads the event data, does its work (processes a file, updates a database, sends a notification), and returns a success response.

  5. If the function fails (throws an exception or returns an error status), Eventarc or Pub/Sub retries delivery according to the configured retry policy. This is why idempotency matters.

Every CloudEvent your function receives has a type field (like google.cloud.storage.object.v1.finalized), a source that identifies which service sent it, and a data field with the payload specific to that event type. Your function reads these from the cloud_event argument.

Idempotency

Event delivery is at-least-once. Your function may be called more than once for the same event if a previous execution failed partway through. An idempotent function produces the same result whether it runs once or ten times with the same input. Use upserts instead of inserts, check for existing records before creating them, or use the CloudEvent ID as a deduplication key.

When to use this

Event-driven Cloud Functions are the right tool when code should run in response to something that happened, not on a schedule or inside a long-running server:

  • Processing uploaded files. A CSV lands in Cloud Storage. A function parses it, validates the rows, and writes results to BigQuery. The uploader does not wait for this to finish.

  • Reacting to Pub/Sub messages. An API receives an order, publishes a message to a topic, and returns immediately. A function processes the order asynchronously without blocking the API response. See the Pub/Sub overview for how topics and subscriptions work.

  • Handling Firestore document changes. A new user document is created. A function sends a welcome email and updates a search index. The main write path stays fast; side effects run in the background.

  • Security and compliance automation. An audit log records a new IAM binding or a bucket creation. A function checks the change against policy, sends an alert, or updates a CMDB.

  • Sending notifications or calling webhooks. Something changes in your system. A function formats the change and posts it to Slack, sends an email, or calls a third-party endpoint.

  • Decoupling slow work from user-facing APIs. Any work that takes more than a second and doesn’t need a synchronous result is a candidate for offloading to a function.

When not to use this

  • Long-running compute. Cloud Functions 2nd gen has a 60-minute timeout, but it isn’t designed for sustained CPU-heavy batch processing. Use Batch or a VM for that.

  • Full web applications. A multi-route HTTP API with middleware, database connection pooling, and complex authentication belongs on Cloud Run, not in a collection of individual functions.

  • Strict synchronous request-response flows. If a user is waiting for an answer and every millisecond counts, a cold-starting function introduces latency. Consider Cloud Run with minimum instances configured, or a direct synchronous API call.

  • Custom container requirements. If you need a specific base image, system binaries, or a non-standard runtime, deploy to Cloud Run directly with a Dockerfile.

  • Guaranteed exactly-once delivery. Neither Pub/Sub nor Eventarc guarantees exactly-once delivery. If you need reliable task delivery with deduplication, rate controls, and configurable delays, look at Cloud Tasks instead. The Pub/Sub vs Cloud Tasks comparison explains when each is the better fit.

Common event-driven patterns

  • File processing pipeline. A file is uploaded to Cloud Storage. A function triggers, transforms it (resize an image, parse a CSV, transcode a video), and writes the result to a different bucket or database. The uploader knows nothing about the processing step.

  • Async task fan-out. An API endpoint receives a request and publishes a Pub/Sub message. A function processes the message asynchronously, keeping the API response time fast and decoupling the work from the HTTP request.

  • Reactive side effects. A Firestore document is written. A function triggers and sends a notification email, updates a search index, or calls an external webhook. The main write path stays fast; side effects happen in the background.

  • Security and compliance automation. An audit log entry records a resource creation. A function triggers and enforces a tagging policy, sends an alert, or updates a CMDB. This is one of the strongest use cases for Eventarc’s audit log triggers. See the event-driven systems architecture guide for how these patterns fit into larger system designs.

Cloud Storage trigger

A Storage trigger fires when a file event occurs in a named bucket. The most common event type is google.cloud.storage.object.v1.finalized, which fires when an upload completes (not when it starts). This guarantees the file is fully written by the time your function runs.

The function receives the bucket name and object name in the event data. Write output to a different bucket. If you write back to the trigger bucket, the new file fires the trigger again.

# main.py
import functions_framework
from google.cloud import storage

@functions_framework.cloud_event
def process_upload(cloud_event):
    data = cloud_event.data
    bucket_name = data["bucket"]
    file_name = data["name"]
    print(f"Processing: gs://{bucket_name}/{file_name}")

    client = storage.Client()
    source_blob = client.bucket(bucket_name).blob(file_name)
    dest_bucket = client.bucket("processed-files-bucket")
    # Write to a different bucket — never back to the trigger bucket
    client.bucket(bucket_name).copy_blob(source_blob, dest_bucket)
# Deploy with a Cloud Storage trigger (fires when file upload completes)
gcloud functions deploy process-upload \
  --gen2 \
  --runtime=python312 \
  --region=us-central1 \
  --source=. \
  --entry-point=process_upload \
  --trigger-event-filters="type=google.cloud.storage.object.v1.finalized" \
  --trigger-event-filters="bucket=raw-uploads-bucket" \
  --service-account=process-fn-sa@PROJECT_ID.iam.gserviceaccount.com
Infinite loop risk

If your function writes output to the same bucket that triggered it, the new file fires the trigger again. That triggers the function again. This loop runs until you delete the function or run out of budget. Always write to a separate output bucket, or check the file name and return early for files your function produced.

IAM

The function service account needs Storage Object Viewer on the source bucket to read the file, and Storage Object Creator on the destination bucket to write output. Trigger delivery is handled by Eventarc, but your function code accesses Cloud Storage directly using its own service account.

Pub/Sub trigger

A Pub/Sub trigger subscribes a function to a topic. Every message published to that topic invokes the function once per message. The message body is base64-encoded inside the CloudEvent payload, so you always decode it before parsing.

Pub/Sub delivers at-least-once. If a function throws an exception, Pub/Sub retries the message. The same message may be delivered more than once, so your function must be idempotent. The Pub/Sub message ID is a reliable deduplication key: it stays the same across retries. See Pub/Sub push vs pull delivery for more on how delivery modes work.

# main.py
import base64
import json
import functions_framework

@functions_framework.cloud_event
def handle_order(cloud_event):
    message_bytes = base64.b64decode(
        cloud_event.data["message"]["data"]
    )
    order = json.loads(message_bytes)
    order_id = order["order_id"]

    # Check if this order was already processed before doing work
    # Pub/Sub delivers at-least-once — the same message may arrive twice
    if already_processed(order_id):
        print(f"Skipping duplicate: {order_id}")
        return

    print(f"Processing order: {order_id}")
    # ... process the order
    mark_as_processed(order_id)
# Deploy with a Pub/Sub trigger
gcloud functions deploy handle-order \
  --gen2 \
  --runtime=python312 \
  --region=us-central1 \
  --source=. \
  --entry-point=handle_order \
  --trigger-topic=orders-topic
Deduplication tip

Store processed order IDs in Firestore or Cloud Memorystore with the Pub/Sub message ID as the key. Before doing any work, check whether that ID already exists. If it does, return early. This pattern keeps functions idempotent without changing your business logic.

Chaining functions with Pub/Sub

When a workflow has more than one step, connect functions with Pub/Sub topics instead of calling them directly. Each function does its work and publishes a message to the next topic in the chain. If step 2 fails, Pub/Sub retries it without re-running step 1: no shared state, no coordination logic needed.

This is how you build reliable, independently-retriable pipelines from small, single-purpose functions. Think of it like an assembly line where each station can be paused and restarted without stopping the whole line.

Upload → [Storage event] → parse-fn → [Pub/Sub: parsed] → enrich-fn → [Pub/Sub: enriched] → store-fn
# enrich-fn: receives a parsed record and publishes an enriched result
import base64
import json
import functions_framework
from google.cloud import pubsub_v1

publisher = pubsub_v1.PublisherClient()
TOPIC = "projects/my-project/topics/enriched-files"

@functions_framework.cloud_event
def enrich(cloud_event):
    record = json.loads(
        base64.b64decode(cloud_event.data["message"]["data"])
    )
    record["enriched"] = True
    publisher.publish(TOPIC, json.dumps(record).encode())
    print(f"Forwarded: {record['id']}")
Double-publish risk

If a function publishes to the next topic and then fails before returning success, Pub/Sub retries the message and the function publishes again. Downstream steps will receive duplicates. Use upserts instead of inserts when writing to a database, and treat every step in the chain as independently idempotent.

For orchestration with conditionals, error branches, and timeouts, consider Cloud Workflows instead of a long pub/sub chain. For straightforward sequential steps, Pub/Sub chaining is easy to reason about and easy to debug with structured logs.

Audit log triggers with Eventarc

Eventarc can trigger functions based on Cloud Audit Log entries. Every time a GCP API call is recorded in the audit log (a VM was deleted, a new IAM binding was created, a BigQuery table was dropped), Eventarc can route that event to a function. This is the standard pattern for security monitoring and compliance automation.

You filter by serviceName (which GCP service produced the log entry) and methodName (which API method was called). Audit Log triggers require the relevant log type (Admin Activity or Data Access) to be enabled in your project for the service you want to watch.

# Trigger when a new Cloud Storage bucket is created
gcloud functions deploy on-bucket-created \
  --gen2 \
  --runtime=python312 \
  --region=us-central1 \
  --source=. \
  --entry-point=on_bucket_created \
  --trigger-event-filters="type=google.cloud.audit.log.v1.written" \
  --trigger-event-filters="serviceName=storage.googleapis.com" \
  --trigger-event-filters="methodName=storage.buckets.insert" \
  --service-account=audit-fn-sa@PROJECT_ID.iam.gserviceaccount.com
# main.py
import functions_framework

@functions_framework.cloud_event
def on_bucket_created(cloud_event):
    log_entry = cloud_event.data.get("protoPayload", {})
    resource_name = log_entry.get("resourceName", "")
    principal = log_entry.get("authenticationInfo", {}).get("principalEmail", "")
    print(f"New bucket: {resource_name} by {principal}")
    # Send alert, update CMDB, enforce tagging policy here
Tip

To find the correct serviceName and methodName values for any GCP API, perform the action manually and inspect the resulting audit log entry in Logs Explorer. The method name appears as the protoPayload.methodName field.

Firestore trigger

Firestore triggers fire when documents in a collection change: created, updated, deleted, or written (any change). You specify the target documents using a path pattern with wildcard segments, so a single trigger can cover all documents in a collection rather than one specific document.

The function receives a CloudEvent containing the document data. Firestore triggers for 2nd gen functions are routed through Eventarc, the same service that handles audit log and custom triggers. The Firestore data model page covers how collections, documents, and subcollections are structured.

# Trigger when any document in the users collection is created
gcloud functions deploy on-user-created \
  --gen2 \
  --runtime=python312 \
  --region=us-central1 \
  --source=. \
  --entry-point=on_user_created \
  --trigger-event-filters="type=google.cloud.firestore.document.v1.created" \
  --trigger-event-filters="database=(default)" \
  --trigger-event-filters-path-pattern="document=users/{userId}"

Cloud Functions vs Cloud Run

Cloud Functions 2nd gen runs on Cloud Run under the hood, but the two offer a different developer experience and suit different workloads. See the full side-by-side on the Cloud Run vs Cloud Functions comparison page.

ConsiderationCloud FunctionsCloud Run
DeploymentDeploy source code directly (no Dockerfile needed)Deploy a container image you build yourself
Event triggersBuilt-in trigger flags (—trigger-topic, —trigger-event-filters)Requires manual Eventarc trigger setup
Number of entry pointsOne function handler per deploymentOne service can handle many routes
Runtime choiceChoose from supported runtimes (Python, Node.js, Go, Java, Ruby, .NET)Any runtime, binary, or base image
Best forSingle-purpose event handlers, webhooks, async tasksFull APIs, background services, custom container requirements

The practical rule: if you are writing code that runs in response to a single event type and don’t want to manage a Dockerfile, Cloud Functions is simpler. If you are building something that handles many HTTP routes, needs a custom runtime, or requires specific container configuration, use Cloud Run directly. The Cloud Run deployment guide covers the container-based workflow step by step.

Common mistakes

  1. Writing output to the same bucket that triggered the function. The new file fires the trigger again, which creates another output file, which fires the trigger again. This loop runs until you manually stop it. Always write to a separate output bucket, or check a file name prefix and return early for files your function produced.

  2. Not making Pub/Sub functions idempotent. Pub/Sub delivers at-least-once. The same message may arrive twice if the function fails after completing work but before returning success. Use upserts, check for existing records before inserting, or deduplicate on the Pub/Sub message ID.

  3. Doing too much in one function. A function that reads a file, calls three external APIs, transforms the data, and sends an email is hard to debug and expensive to retry when one step fails. Break workflows into a chain of small functions connected by Pub/Sub topics, where each step is independently retriable.

  4. Missing IAM permissions on the function service account. A Storage-triggered function needs Storage Object Viewer to read the triggering file. A Pub/Sub-triggered function needs no special Pub/Sub permissions for delivery, but needs permissions for every resource it reads or writes downstream. Check the service account before assuming a permission error is a code bug.

  5. Not thinking about duplicate events. Even outside Pub/Sub, Eventarc can deliver the same event more than once in failure conditions. Treat idempotency as a design requirement, not an optimisation.

  6. Weak observability. Event-driven pipelines fail silently. A function that throws retries, but without error rate alerts and structured logs, failures accumulate unnoticed. Add structured logging to every function and set up an alert on its error rate metric in Cloud Monitoring.

  7. Assuming every async workflow should use a function. If you need to enqueue a single task with a specific delay or retry schedule, Cloud Tasks is a better fit than Pub/Sub. If the workflow has conditionals, waits, and multiple error paths, Cloud Workflows gives you more control. Not every async job belongs in Cloud Functions.

Design tips

  • Keep functions small and single-purpose. Each function should do one thing. This makes them testable, independently retriable, and easy to replace or update.

  • Use separate output locations. Write results to a different bucket, collection, or topic than the one that triggered the function.

  • Make all writes idempotent. Prefer upserts over inserts. Store a processed-IDs set or use event IDs as deduplication keys.

  • Prefer event chaining for complex workflows. A chain of small functions connected by Pub/Sub topics is easier to debug and retry than one large function that does everything.

  • Use least-privilege service accounts. Create a dedicated service account per function and grant only the roles it actually needs. The Cloud Run security model applies here too, since 2nd gen functions run on Cloud Run and use the same IAM and secret patterns.

  • Add structured logging. Log the event ID, resource name, and outcome for every invocation. Structured logs are queryable in Logs Explorer and essential for tracing events through a multi-step pipeline.

  • Test failure and retry behaviour explicitly. Deliberately throw exceptions in development and confirm that retries produce the correct result without creating duplicates.

Frequently asked questions

What is Eventarc and how does it relate to Cloud Functions?

Eventarc is the GCP event routing service that delivers events from over 90 GCP services to Cloud Functions (2nd gen), Cloud Run, and GKE. It uses the CloudEvents standard format. When you deploy a 2nd gen function with --trigger-event-filters, GCP creates an Eventarc trigger under the hood. Pub/Sub and Storage triggers set this up automatically. For audit log triggers or custom event sources, you interact with Eventarc more directly.

What events can trigger a Cloud Function?

Cloud Functions 2nd gen supports: Cloud Storage events (object finalized, deleted, archived), Pub/Sub messages, Firestore document changes (created, updated, deleted), Cloud Audit Log entries for almost any GCP API action, and any event source supported by Eventarc including third-party sources. HTTP triggers are also available for webhooks and synchronous APIs.

What is the difference between Pub/Sub and Eventarc triggers?

Pub/Sub triggers subscribe a function to a topic. Messages published to that topic invoke the function. Eventarc triggers route events from GCP services using structured CloudEvents. For message-passing patterns, a Pub/Sub trigger is simpler. Eventarc is the right choice when reacting to GCP API activity, Cloud Audit Logs, or third-party event sources. Under the hood, 2nd gen Cloud Functions uses Eventarc for all trigger types.

Can Cloud Functions call other GCP services?

Yes. A function can call any GCP service its service account has permission to access: read from Cloud Storage, write to Firestore or BigQuery, publish to Pub/Sub, call a Cloud Run service, or make external HTTP requests. The function service account must have IAM roles for each downstream service it uses. Always create a dedicated service account rather than using the Compute Engine default.

How do I avoid duplicate processing in event-driven systems?

Pub/Sub delivers at-least-once, so the same message may arrive more than once if a function throws before acknowledging. Make functions idempotent: check if the work was already done before doing it. Practical approaches include using the Pub/Sub message ID as a deduplication key, using database upserts instead of inserts, or storing a processed-IDs set in Firestore. For Eventarc audit log triggers, the event ID in the CloudEvent envelope can serve as a deduplication key.

Last verified: 22 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.