← Back to list

Building a Private Dialogflow CX Webhook Using Cloud Functions, Service Directory, and an Internal…

Modern conversational applications often need to interact with backend systems that must remain private. If your organization uses VPC…

Bolaji Akerele · 2025-12-10 00:49 · 0 claps · 3.3 min read
#google-cloud #dialogflow #cloud-functions #vpc-service-control #serverless
Open on Medium ↗
Wiki topics: 🌐 · Web Development ☁️ · DevOps & Cloud 🎬 · Film & Television

Building a Private Dialogflow CX Webhook Using Cloud Functions, Service Directory, and an Internal Load Balancer (with Bash & Terraform)

Modern conversational applications often need to interact with backend systems that must remain private. If your organization uses VPC Service Controls, or you simply want to avoid exposing backend endpoints on the public internet, you’ll need a secure architecture for Dialogflow CX webhooks.

Unfortunately, Cloud Functions can’t be called directly by Dialogflow CX inside a VPC-SC perimeter. But you can reach them privately by inserting a lightweight internal routing layer.

This guide walks you through an updated architecture using:

  • Dialogflow CX private service routing
  • Service Directory
  • Internal TCP Load Balancer
  • GCE instance running a simple reverse proxy (Nginx)
  • Internal-only Cloud Function

Every step includes both Bash (gcloud) and Terraform options.

🧱 Architecture Overview

Dialogflow CX ⟶ Service Directory ⟶ Internal TCP Load Balancer ⟶ GCE Reverse Proxy ⟶ Cloud Function (Internal)

Why this works:

  • Dialogflow CX can call internal endpoints resolved via Service Directory.
  • The ILB distributes traffic across private backend instances
  • A tiny GCE VM (no public IP) proxies all requests to your Cloud Function.
  • The Cloud Function is set to internal-only ingress, protecting it from public access.

1. Prerequisites

GCP project with billing enabled

IAM roles:

  • Compute Admin
  • Service Directory Admin
  • Cloud Functions Developer
  • Dialogflow API Admin

gcloud CLI, or Terraform installed locally

2. Set Environment Variables (Bash)

export PROJECT_ID="my-dialogflow-secure"
export REGION="europe-west1"
export NETWORK="df-vpc"
export SUBNET="df-subnet"
export ILB_IP="10.10.20.99"
export ZONE="${REGION}-b"
export INSTANCE_NAME="df-proxy"
export INSTANCE_GROUP="df-proxy-ig"
export SERVICE_NAME="df-webhook"

3. Enable Required APIs

Bash

gcloud services enable \
  compute.googleapis.com \
  servicedirectory.googleapis.com \
  dialogflow.googleapis.com \
  cloudfunctions.googleapis.com \
  vpcaccess.googleapis.com

Terraform

resource "google_project_service" "services" {
  for_each = toset([
    "compute.googleapis.com",
    "servicedirectory.googleapis.com",
    "dialogflow.googleapis.com",
    "cloudfunctions.googleapis.com",
    "vpcaccess.googleapis.com"
  ])
  service = each.value
}

4. Create VPC + Subnet

Bash

gcloud compute networks create $NETWORK --subnet-mode=custom
gcloud compute networks subnets create $SUBNET \
  --network=$NETWORK \
  --range=10.10.20.0/24 \
  --region=$REGION \
  --enable-private-ip-google-access

Terraform

resource "google_compute_network" "vpc" {
  name = var.network_name
}

resource "google_compute_subnetwork" "subnet" {
  name                     = var.subnet_name
  region                   = var.region
  ip_cidr_range            = "10.10.20.0/24"
  network                  = google_compute_network.vpc.id
  private_ip_google_access = true
}

5. Deploy GCE Proxy Instance (Nginx TCP Proxy)

The VM has no external IP.

Startup Script Example

#! /bin/bash
apt-get update
apt-get install -y nginx
cat <<EOF > /etc/nginx/nginx.conf
events {}
stream {
    upstream cloud_fn {
        server YOUR_CLOUD_FUNCTION_URL:443;
    }
    server {
        listen 8080;
        proxy_pass cloud_fn;
    }
}
EOF
systemctl restart nginx

Bash

gcloud compute instances create $INSTANCE_NAME \
  --zone=$ZONE \
  --machine-type=e2-micro \
  --network-interface=network=$NETWORK,subnet=$SUBNET,no-address \
  --metadata-from-file startup-script=nginx-startup.sh

Terraform

resource "google_compute_instance" "proxy" {
  name         = var.instance_name
  machine_type = "e2-micro"
  zone         = var.zone
  boot_disk { initialize_params { image = "debian-cloud/debian-12" } }
  network_interface {
    network    = google_compute_network.vpc.id
    subnetwork = google_compute_subnetwork.subnet.id
    # no external IP = private only
  }
  metadata = {
    startup-script = file("${path.module}/nginx-startup.sh")
  }
}

6. Create Internal Load Balancer

Bash

gcloud compute instance-groups unmanaged create $INSTANCE_GROUP \
  --zone=$ZONE
gcloud compute instance-groups unmanaged add-instances $INSTANCE_GROUP \
  --instances=$INSTANCE_NAME \
  --zone=$ZONE
gcloud compute health-checks create tcp df-health --port 8080
gcloud compute backend-services create df-backend \
  --load-balancing-scheme=INTERNAL \
  --protocol=TCP \
  --region=$REGION \
  --health-checks=df-health
gcloud compute backend-services add-backend df-backend \
  --instance-group=$INSTANCE_GROUP \
  --instance-group-zone=$ZONE \
  --region=$REGION
gcloud compute forwarding-rules create df-ilb \
  --load-balancing-scheme=INTERNAL \
  --address=$ILB_IP \
  --ports=8080 \
  --backend-service=df-backend \
  --region=$REGION

Terraform

resource "google_compute_region_backend_service" "backend" {
  name                  = "df-backend"
  region                = var.region
  protocol              = "TCP"
  load_balancing_scheme = "INTERNAL"
  health_checks         = [google_compute_health_check.tcp.id]

  backend {
    group = google_compute_instance_group.proxy.self_link
  }
}

7. Register Service Directory Entry

Bash

gcloud service-directory namespaces create webhook-ns --location=$REGION
gcloud service-directory services create $SERVICE_NAME \
  --namespace=webhook-ns \
  --location=$REGION
gcloud service-directory endpoints create webhook-endpoint \
  --service=$SERVICE_NAME \
  --namespace=webhook-ns \
  --location=$REGION \
  --address=$ILB_IP \
  --port=8080

Terraform

resource "google_service_directory_namespace" "ns" {
  provider = google
  namespace = "webhook-ns"
  location  = var.region
}

resource "google_service_directory_service" "svc" {
  name      = var.service_name
  namespace = google_service_directory_namespace.ns.id
}

resource "google_service_directory_endpoint" "endpoint" {
  name      = "df-endpoint"
  service   = google_service_directory_service.svc.id
  address   = var.ilb_ip
  port      = 8080
}

8. Deploy Cloud Function (Internal-Only)

Bash

gcloud functions deploy df-webhook-fn \
  --region=$REGION \
  --runtime=nodejs20 \
  --entry-point=webhook \
  --source=./function \
  --trigger-http \
  --no-allow-unauthenticated \
  --ingress-settings=internal-only \
  --vpc-connector=my-connector

Terraform

resource "google_cloudfunctions2_function" "fn" {
  name     = "df-webhook-fn"
  location = var.region
  build_config {
    runtime     = "nodejs20"
    entry_point = "webhook"
    source {
      storage_source {
        bucket = google_storage_bucket.src.name
        object = google_storage_bucket_object.code.name
      }
    }
  }
  service_config {
    ingress_settings = "ALLOW_INTERNAL_ONLY"
    vpc_connector    = google_vpc_access_connector.connector.id
  }
}

9. Configure Dialogflow CX Webhook

In Dialogflow CX Console → Manage → Webhooks:

URL format:

https://REGION-dns.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/namespaces/webhook-ns/services/df-webhook
  • Authentication: Dialogflow Service Agent
  • Test the flow — requests should route privately through:

Dialogflow → Service Directory → ILB → GCE Proxy → Cloud Function

🎉 Conclusion

With this architecture, you get:

✔ Fully private communication (satisfies VPC-SC) ✔ Flexible backend logic via Cloud Functions ✔ Scalable internal traffic routing ✔ No public IPs, no exposed endpoints

The Bash + Terraform dual approach ensures readers at any skill level can follow and build reproducible infrastructure.


메타데이터
post_id
eeb7580f12ff
slug
building-a-private-dialogflow-cx-webhook-using-cloud-functions-service-directory-and-an-internal-eeb7580f12ff
url
https://medium.com/@bolajiakerele2013/building-a-private-dialogflow-cx-webhook-using-cloud-functions-service-directory-and-an-internal-eeb7580f12ff
canonical_url
https://medium.com/@bolajiakerele2013/building-a-private-dialogflow-cx-webhook-using-cloud-functions-service-directory-and-an-internal-eeb7580f12ff
author_url
https://medium.com/@bolajiakerele2013
status
ok
fetched_at
2026-07-25 15:44:25