DEV Community

Hello Nakama
Hello Nakama

Posted on

Monitoring Google's Edits to Your Business Profiles with Pub/Sub Notifications

To monitor Google's changes to your Business Profiles, subscribe your account to GOOGLE_UPDATE notifications through the My Business Notifications API and Cloud Pub/Sub. When one arrives, call locations.getGoogleUpdated to see which fields changed in the diffMask, compare them with your source of truth, then accept or reject each change with locations.patch.

Updated September 2026.

Google does not only show what you submit. Its developer guide on managing Google updates says Google uses sources like user reports and licensed content, and notifies listing owners when their information is reported as incorrect. If nobody is watching, a wrong phone number suggested by a stranger can sit on a profile for weeks.

This post builds a small monitoring loop using only documented Google features.

What notifications can the Business Profile APIs send?

The NotificationSetting reference lists the types. The ones useful for monitoring listing data are:

Type What it means
GOOGLE_UPDATE The location has Google updates for review
DUPLICATE_LOCATION The duplicate location field in location metadata changed
VOICE_OF_MERCHANT_UPDATED The location's Voice of Merchant status changed
NEW_CUSTOMER_MEDIA A Google Maps user added a photo or video
NEW_REVIEW, UPDATED_REVIEW Review activity

Skip the question and answer types. The reference marks NEW_QUESTION, UPDATED_QUESTION, NEW_ANSWER, and UPDATED_ANSWER as deprecated, because Google discontinued the Q&A API on November 3, 2025.

VOICE_OF_MERCHANT_UPDATED is worth including. The reference describes Voice of Merchant as whether the location is in good standing and the merchant has control over the business on Google. Losing it is often the first sign of a verification or suspension problem.

How do you set up the Pub/Sub topic?

Google's real-time notifications guide lists the steps: create a topic, give mybusiness-api-pubsub@system.gserviceaccount.com at least publish permission, create a subscription, then link your Business Profile account to the topic.

PROJECT=my-gcp-project
gcloud config set project "$PROJECT"

gcloud pubsub topics create gbp-notifications

gcloud pubsub topics add-iam-policy-binding gbp-notifications \
  --member="serviceAccount:mybusiness-api-pubsub@system.gserviceaccount.com" \
  --role="roles/pubsub.publisher"

gcloud pubsub subscriptions create gbp-notifications-worker \
  --topic=gbp-notifications
Enter fullscreen mode Exit fullscreen mode

How do you link the Business Profile account?

Call accounts.updateNotificationSetting. There is one notification setting per account, and one topic per setting. If you manage several accounts, repeat this for each.

# link_notifications.py
# pip install google-auth requests
from google.auth.transport.requests import AuthorizedSession
from google.oauth2.credentials import Credentials

SCOPES = ["https://www.googleapis.com/auth/business.manage"]
ACCOUNT = "accounts/1234567890"
TOPIC = "projects/my-gcp-project/topics/gbp-notifications"

session = AuthorizedSession(Credentials.from_authorized_user_file("token.json", SCOPES))

resp = session.patch(
    f"https://mybusinessnotifications.googleapis.com/v1/{ACCOUNT}/notificationSetting",
    params={"updateMask": "pubsubTopic,notificationTypes"},
    json={
        "name": f"{ACCOUNT}/notificationSetting",
        "pubsubTopic": TOPIC,
        "notificationTypes": [
            "GOOGLE_UPDATE",
            "DUPLICATE_LOCATION",
            "VOICE_OF_MERCHANT_UPDATED",
        ],
    },
)
resp.raise_for_status()
print(resp.json())
Enter fullscreen mode Exit fullscreen mode

To stop notifications later, the reference says to send an empty notificationTypes list or an empty pubsubTopic.

What does a notification contain?

Very little, by design. It tells you the type and which location, not the new value. Google's guide says the locationName field on a GOOGLE_UPDATE notification provides the resource name of the location. Log the raw payload during your first test so you can confirm the exact field names your subscription receives, and normalize the location name to the locations/{id} form the Business Information API expects.

How do you see what Google changed?

Call locations.getGoogleUpdated with a readMask. The response has three parts:

  • location: the Google-updated version of the location.
  • diffMask: the fields where what customers see differs from your preferred values.
  • pendingMask: fields where your own edit is still processing.

Google's guide says you must accept or reject the fields in diffMask, and you do not need to act on pendingMask.

What does the worker look like?

This worker pulls messages, fetches the Google-updated location, compares the changed fields with your own records, and writes a review item. It does not change anything on its own.

# worker.py
# pip install google-cloud-pubsub google-auth requests
import json

from google.auth.transport.requests import AuthorizedSession
from google.cloud import pubsub_v1
from google.oauth2.credentials import Credentials

SCOPES = ["https://www.googleapis.com/auth/business.manage"]
BIZ = "https://mybusinessbusinessinformation.googleapis.com/v1"
READ_MASK = "name,title,phoneNumbers,storefrontAddress,websiteUri,regularHours,categories"
WATCHED = ("title", "phoneNumbers", "storefrontAddress", "websiteUri", "regularHours", "categories")

session = AuthorizedSession(Credentials.from_authorized_user_file("token.json", SCOPES))


def normalize(name: str) -> str:
    # accept "accounts/1/locations/2" or "locations/2"
    return name[name.index("locations/"):]


def source_of_truth(location_name: str) -> dict:
    # Replace with a lookup in your own location database.
    return {}


def handle(payload: dict) -> None:
    raw = payload.get("locationName") or payload.get("location")
    if not raw:
        print("unrecognized payload", payload)
        return
    loc = normalize(raw)
    resp = session.get(f"{BIZ}/{loc}:getGoogleUpdated", params={"readMask": READ_MASK})
    resp.raise_for_status()
    body = resp.json()
    changed = [f for f in body.get("diffMask", "").split(",") if f]
    watched = [f for f in changed if f.startswith(WATCHED)]
    if not watched:
        return
    ours = source_of_truth(loc)
    review_item = {
        "location": loc,
        "fields": watched,
        "google_value": {f.split(".")[0]: body["location"].get(f.split(".")[0]) for f in watched},
        "our_value": {f.split(".")[0]: ours.get(f.split(".")[0]) for f in watched},
        "pending": body.get("pendingMask", ""),
    }
    print(json.dumps(review_item, indent=2))  # send to your ticket queue instead


def callback(message) -> None:
    try:
        payload = json.loads(message.data.decode("utf-8"))
        if payload.get("type", "GOOGLE_UPDATE") == "GOOGLE_UPDATE":
            handle(payload)
        else:
            print("other notification", payload)
        message.ack()
    except Exception as exc:
        print("failed, will retry:", exc)
        message.nack()


subscriber = pubsub_v1.SubscriberClient()
path = subscriber.subscription_path("my-gcp-project", "gbp-notifications-worker")
future = subscriber.subscribe(path, callback=callback)
print("listening on", path)
future.result()
Enter fullscreen mode Exit fullscreen mode

Two design notes. The nack on failure lets Pub/Sub redeliver, so a temporary API error does not lose an event. And the worker only reports. Deciding is a human job.

How do you accept or reject a change?

Both use locations.patch with an updateMask covering the fields you are resolving. Google's guide is explicit:

  • Accept: patch the location with the new value from getGoogleUpdated.
  • Reject: patch the location with your original preferred value.
def resolve(loc: str, field: str, value) -> dict:
    resp = session.patch(f"{BIZ}/{loc}", params={"updateMask": field}, json={field: value})
    resp.raise_for_status()
    return resp.json()
Enter fullscreen mode Exit fullscreen mode

Afterward, call getGoogleUpdated again. Google's guide says a successful patch shows "diffMask": "" and the hasGoogleUpdated flag in metadata as false or absent, and it recommends building logic to verify both.

What if you cannot use notifications?

Poll instead. Google's guide notes that locations.get returns a metadata.hasGoogleUpdated flag. A nightly job that checks the flag for every location, then calls getGoogleUpdated only where it is true, is slower but simple.

Where does this fit with a listing platform?

If a listing platform is your source of truth, keep the platform in charge of writes and use this loop as a separate check on what Google is actually serving. Synup, for example, holds location records and flags per-location sync issues, and your worker's source_of_truth function can read from that record instead of a spreadsheet. Make sure only one system writes each field, or the two will fight.

FAQ

Does a GOOGLE_UPDATE notification include the new value?

No. It identifies the location. Call getGoogleUpdated to see the Google-updated values and the diffMask.

Should I auto-reject every Google update?

No. Some updates are correct, such as a fixed pin or a real hours change a manager forgot to report. Route them to a person.

How many topics can one account use?

One. The reference says each account has one notification setting and one Pub/Sub topic.

Can I monitor Q&A this way?

No. The Q&A notification types are deprecated because the Q&A API was discontinued on November 3, 2025.

Top comments (0)