Synchronize cases using Pub/Sub

You can synchronize your support cases in real time between Google Cloud and your Customer Relationship Management (CRM) system (such as Jira Service Desk, Zendesk, or ServiceNow) by building an event-driven connector using Pub/Sub and Cloud Customer Care's event subscription service.

Advantages of an event-driven connector

Compared to a polling-based connector, an event-driven connector offers several significant benefits:

  • Real-time synchronization: Your CRM is updated immediately when a case, comment, or attachment is created or modified in Customer Care, rather than waiting for the next polling interval.
  • Reduced API quota consumption: You don't need to continually query the Cloud Support API (CSAPI) to check for changes, which preserves your API quota for actual read and write operations.
  • Simpler scheduling: You don't need to manage periodic background jobs or cron schedules to continuously poll Google Cloud for updates.

High-level architecture

The event-driven connector relies on Google Cloud's Event Subscription service and Pub/Sub to deliver real-time notifications to your application.

When an action occurs on a support case in Google Cloud, the Event Subscription service publishes a notification message to a customer-owned Pub/Sub topic. Pub/Sub then delivers this message to your connector application—typically through a push subscription to a webhook endpoint hosted on Cloud Run, App Engine, or your internal infrastructure.

The following sequence describes the event-driven synchronization flow from Google Cloud to your CRM:

  1. A user or system performs an action on a case in Customer Care (for example, creating a case, adding a comment, or uploading an attachment).
  2. The Cloud Support API publishes an event notification to your configured Pub/Sub topic.
  3. Pub/Sub pushes the notification to your connector's webhook endpoint.
  4. The connector receives the notification, extracts the resource name from the message attributes, and calls the Cloud Support API to retrieve the full, latest details of the modified resource.
  5. The connector updates your CRM system with the retrieved data.

When changes occur in your CRM, your connector should continue to push those updates to Customer Care by directly calling the Cloud Support API (for example, calling cases.patch or cases.comments.create), just as in the polling-based architecture.

Prerequisites and setup

To set up an event-driven connector, you must configure the necessary Google Cloud resources and Identity and Access Management (IAM) permissions.

1. Enable required APIs

Ensure that both the Cloud Support API and the Pub/Sub API are enabled in the Google Cloud project where you are deploying your connector and Pub/Sub topic:

  • cloudsupport.googleapis.com
  • pubsub.googleapis.com

2. Create a Pub/Sub topic

Create a Pub/Sub topic in your Google Cloud project to receive support event notifications:

gcloud pubsub topics create support-events \
    --project=[PROJECT_ID]

Replace [PROJECT_ID] with your Google Cloud project ID.

3. Grant publisher permissions to Customer Care

To allow Customer Care to publish event notifications to your Pub/Sub topic, you must grant the Pub/Sub Publisher role (roles/pubsub.publisher) on your topic to the Google Support event publishing service account:

cloud-support-apievents@system.gserviceaccount.com

Run the following Google Cloud CLI command to grant this permission:

gcloud pubsub topics add-iam-policy-binding projects/[PROJECT_ID]/topics/[TOPIC_NAME] \
    --member="serviceAccount:cloud-support-apievents@system.gserviceaccount.com" \
    --role="roles/pubsub.publisher"

Replace [TOPIC_NAME] with the name of your Pub/Sub topic (for example, support-events).

4. Create a support event subscription

Once your topic is created and permissions are granted, create a SupportEventSubscription resource using the Cloud Support API to begin receiving event notifications for your organization.

You can create the subscription using the REST API:

curl -X POST \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \
  "https://cloudsupport.googleapis.com/v2/organizations/[ORGANIZATION_ID]/supportEventSubscriptions" \
  -d '{
    "pubSubTopic": "projects/[PROJECT_ID]/topics/[TOPIC_NAME]"
  }'

Replace [ORGANIZATION_ID] with your Google Cloud organization ID.

If successful, the API returns the created SupportEventSubscription resource. Ensure that the state field in the response is set to WORKING:

{
  "name": "organizations/123456789/supportEventSubscriptions/987654321",
  "pubSubTopic": "projects/my-project/topics/support-events",
  "state": "WORKING",
  "createTime": "2026-07-08T12:00:00Z",
  "updateTime": "2026-07-08T12:00:00Z"
}

Pub/Sub message format

When an event occurs, Customer Care publishes a standard Pub/Sub message to your topic. To minimize payload size and avoid transmitting sensitive data directly through Pub/Sub, the message data field is empty. Instead, all event metadata is provided in the message attributes.

The attributes map contains the following key-value pairs:

Attribute Description Example
resource_name The full relative resource name of the Cloud Support API resource that was created or modified. organizations/123/cases/456
organizations/123/cases/456/comments/789
organizations/123/cases/456/attachments/012
notification_type The type of event that triggered the notification. CASE_CREATED
CASE_UPDATED
COMMENT_ADDED
ATTACHMENT_ADDED
ATTACHMENT_DELETED

Notification types

The notification_type attribute indicates what action occurred:

  • CASE_CREATED: A new support case was created.
  • CASE_UPDATED: An existing case was updated (for example, priority, status, or title changed).
  • COMMENT_ADDED: A new comment was added to a case.
  • ATTACHMENT_ADDED: A new attachment was uploaded to a case.
  • ATTACHMENT_DELETED: An attachment was deleted from a case.

Connector implementation example

The following example demonstrates how to implement a webhook handler in Python using the Flask framework to receive Pub/Sub push notifications and synchronize changes to your CRM.

Webhook handler

When configuring your Pub/Sub push subscription, point the endpoint URL to your Flask application's /webhook route:

import base64
import json
from flask import Flask, jsonify, request
import googleapiclient.discovery

app = Flask(__name__)

# Initialize the Cloud Support API client
api_version = "v2"
support_api = googleapiclient.discovery.build(
    serviceName="cloudsupport",
    version=api_version,
    discoveryServiceUrl=f"https://cloudsupport.googleapis.com/$discovery/rest?version={api_version}",
)

@app.route("/webhook", methods=["POST"])
def pubsub_push_handler():
    """Handles incoming Pub/Sub push messages."""
    envelope = request.get_json()
    if not envelope:
        return "Bad Request: no envelope received", 400

    pubsub_message = envelope.get("message")
    if not pubsub_message:
        return "Bad Request: no message received", 400

    attributes = pubsub_message.get("attributes", {})
    resource_name = attributes.get("resource_name")
    notification_type = attributes.get("notification_type")

    if not resource_name or not notification_type:
        # Acknowledge message to prevent Pub/Sub from retrying malformed messages
        return "Missing required attributes", 200

    try:
        # Process the event based on resource type and notification type
        process_support_event(resource_name, notification_type)
    except Exception as e:
        # Log error and return 500 so Pub/Sub will retry delivery
        print(f"Error processing event for {resource_name}: {e}")
        return "Internal Server Error", 500

    # Acknowledge successful processing to Pub/Sub
    return "", 204


def process_support_event(resource_name, notification_type):
    """Fetches resource details from CSAPI and synchronizes with CRM."""
    if "/comments/" in resource_name:
        sync_comment(resource_name, notification_type)
    elif "/attachments/" in resource_name:
        sync_attachment(resource_name, notification_type)
    elif "/cases/" in resource_name:
        sync_case(resource_name, notification_type)
    else:
        print(f"Unrecognized resource type in name: {resource_name}")


def sync_case(case_name, notification_type):
    """Retrieves case details and updates CRM."""
    case = support_api.cases().get(name=case_name).execute()

    # Implement CRM-specific logic to create or update the case
    # For example:
    # if notification_type == "CASE_CREATED":
    #     crm_client.create_ticket(summary=case.get("displayName"), ...)
    # elif notification_type == "CASE_UPDATED":
    #     crm_client.update_ticket(status=case.get("state"), ...)
    print(f"Synced case {case_name} ({notification_type})")


def sync_comment(comment_name, notification_type):
    """Retrieves comment details and adds it to CRM."""
    if notification_type == "COMMENT_ADDED":
        comment = support_api.cases().comments().get(name=comment_name).execute()

        # Extract parent case name (e.g., organizations/123/cases/456)
        case_name = comment_name.split("/comments/")[0]

        # Implement CRM-specific logic to append the comment to the corresponding ticket
        # crm_client.add_comment(ticket_id=lookup_crm_id(case_name), body=comment.get("body"))
        print(f"Synced comment {comment_name} for case {case_name}")


def sync_attachment(attachment_name, notification_type):
    """Handles attachment additions or deletions in CRM."""
    if notification_type == "ATTACHMENT_ADDED":
        attachment = support_api.cases().attachments().get(name=attachment_name).execute()
        case_name = attachment_name.split("/attachments/")[0]

        # Implement CRM-specific logic to download media from CSAPI and attach to CRM ticket
        print(f"Synced attachment {attachment_name} for case {case_name}")
    elif notification_type == "ATTACHMENT_DELETED":
        # Handle attachment removal if supported by your CRM
        print(f"Processed attachment deletion: {attachment_name}")


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

Handling outages and circuit breakers

Customer Care includes built-in circuit breaker mechanisms to protect both your infrastructure and Google's systems during outages or misconfigurations.

Subscription failure states

If Customer Care is repeatedly unable to publish messages to your Pub/Sub topic (for example, if the topic is deleted or the IAM publishing permission is revoked), the state of your SupportEventSubscription transitions from WORKING to FAILING. When in the FAILING state, Customer Care temporarily suspends publishing event notifications for that subscription.

You can inspect the failure_reason field of the subscription to determine why publishing is failing:

  • PERMISSION_DENIED: The service account (cloud-support-apievents@system.gserviceaccount.com) lacks the pubsub.publisher permission on your topic.
  • TOPIC_NOT_FOUND: The configured Pub/Sub topic does not exist.
  • OTHER: Publishing failed due to another transient or system-side error.

Monitoring and recovery

To ensure reliable synchronization, we recommend setting up monitoring and alerting:

  1. Monitor event processing and subscription health: Monitor event processing throughput, latency, and error rates on your connector service to alert your team if processing drops below expected baselines. You can also periodically query your SupportEventSubscription resource using the Cloud Support API to verify if any subscription enters the FAILING state.
  2. Resolve underlying issues: Restore the missing IAM permissions or recreate the missing Pub/Sub topic.
  3. Resume synchronization: Once the underlying issue is resolved, issue an update call to UpdateSupportEventSubscription providing your pub_sub_topic (a no-op update with your existing topic name is sufficient). When called, Customer Care sends a test notification to verify topic access, automatically resetting the subscription state back to WORKING and clearing failure_reason once verified.

If an outage occurs and your webhook is temporarily unreachable, Pub/Sub automatically retries message delivery according to your topic and subscription's retry policies. If an outage exceeds your Pub/Sub message retention window, your connector should perform a fallback polling query (using cases.search with a time filter) to catch up on any missed updates during the downtime.

What's next