# How to Send Google Cloud Build Success Alerts to Your Team's Phones

> Use an authenticated Pub/Sub push receiver to filter one Google Cloud Build trigger, then send fixed, replay-safe copy to a shared PingRoom Room.

- Published: 2026-10-29
- Author: PingRoom
- Category: Automation
- Topics: Google Cloud Build notifications, Google Cloud Build success alert, CI CD notifications, automation notifications to phone
- Canonical: https://pingroom.io/blog/google-cloud-build-success-push-notifications
- Cover image: https://pingroom.io/blog/google-cloud-build-success-push-notifications-cover.webp
- Cover description: Two PingRoom avatars guide one finished clay build token toward a sealed signal chute while Pingo watches beside a closed build folio.

**To send one Google Cloud Build success alert to a small team's phones, publish Cloud Build updates to Pub/Sub, push them to a private Cloud Run receiver, filter one trigger's `SUCCESS` event, and send fixed copy to a PingRoom incoming webhook.** Use the Pub/Sub message ID for replay safety, and keep build details in Google Cloud.

This is a webhook recipe, not a native Google Cloud integration. The destination Room owner needs **PingRoom Pro**. The Google Cloud project needs Cloud Build, Pub/Sub, Cloud Run, IAM, and Secret Manager configured by someone with the required permissions.

## Choose the smallest path that reaches the right people

Google Cloud already maintains [notifiers for Slack, SMTP email, Google Chat, GitHub Issues, HTTP endpoints, and BigQuery](https://docs.cloud.google.com/build/docs/configuring-notifications/notifiers). Start there when the people who need the update already use one of those channels.

| Need                                                               | Better starting point                                         |
| ------------------------------------------------------------------ | ------------------------------------------------------------- |
| A developer needs logs, artifacts, or a rerun                      | Cloud Build history                                           |
| The engineering team already works in Slack or Google Chat         | Cloud Build's first-party notifier                            |
| Email reaches the intended people                                  | Cloud Build's SMTP notifier                                   |
| One selected success should reach a separate small group of phones | Authenticated Pub/Sub push → private receiver → PingRoom Room |
| A failed release needs paging, escalation, or an on-call record    | Your incident system                                          |

The phone alert below means only that one selected Cloud Build trigger finished with `SUCCESS`. It does not prove that an artifact is safe, a deployment happened, production is healthy, or every phone displayed the notification.

## Create the private Room and receiver

Create or choose a private Room for the stable group that needs the signal. Open **Settings → Connections → From another app**, add an incoming webhook, and copy its complete URL. The [incoming-webhook walkthrough](/blog/webhook-to-push-notification) covers the Room-side setup, while the [webhook reference](/documents/webhooks) documents the current fields, limits, cooldown, and replay behavior.

Treat the URL as a credential. Store it in Google Secret Manager and expose it to the private Cloud Run service as `PINGROOM_WEBHOOK_URL`. Google recommends [Secret Manager for sensitive Cloud Run configuration](https://docs.cloud.google.com/run/docs/configuring/services/secrets). Pin an environment-variable reference to a specific secret version, or mount a secret volume when your rotation process needs the latest version.

Give the receiver three settings:

- `CLOUD_BUILD_SUBSCRIPTION`: the full subscription name, such as `projects/example-project/subscriptions/pingroom-build-success`;
- `CLOUD_BUILD_TRIGGER_ID`: the exact ID of the one trigger allowed to notify the Room;
- `PINGROOM_WEBHOOK_URL`: the complete secret PingRoom incoming-webhook URL.

Deploy the receiver as a private Cloud Run service. Do not allow unauthenticated invocation. The Pub/Sub push service account gets `roles/run.invoker`; other callers do not.

For Cloud Run, Google performs the ID-token validation and IAM authorization before your handler runs. If you host the receiver elsewhere, validate the Pub/Sub JWT signature, audience, verified email, and expected service-account email in application code before parsing the event.

## Filter one trigger and forward fixed copy

Cloud Build [publishes a Pub/Sub envelope](https://docs.cloud.google.com/build/docs/subscribe-build-notifications) with `status` and `buildId` attributes. The base64 `message.data` field contains the Build resource. Check both representations so a malformed envelope cannot turn another event into a success signal.

The dependency-free handler below assumes Cloud Run already admitted the authenticated Pub/Sub caller. Adapt its exported function to your framework's POST route.

```js
const MAX_BODY_BYTES = 256_000;
const SAFE_ID = /^[A-Za-z0-9._:-]{1,255}$/;

function reply(status, body = null) {
  if (body === null) return new Response(null, { status });
  return Response.json(body, { status });
}

function decodeBase64Json(value) {
  if (typeof value !== "string" || value.length > 400_000) return null;

  try {
    const bytes = Uint8Array.from(atob(value), (character) =>
      character.charCodeAt(0),
    );
    return JSON.parse(new TextDecoder().decode(bytes));
  } catch {
    return null;
  }
}

export async function handleCloudBuildPush(request, env, fetchImpl = fetch) {
  if (request.method !== "POST") {
    return reply(405, { error: "method_not_allowed" });
  }

  const {
    CLOUD_BUILD_SUBSCRIPTION,
    CLOUD_BUILD_TRIGGER_ID,
    PINGROOM_WEBHOOK_URL,
  } = env;

  if (
    !CLOUD_BUILD_SUBSCRIPTION?.startsWith("projects/") ||
    !SAFE_ID.test(CLOUD_BUILD_TRIGGER_ID ?? "") ||
    !PINGROOM_WEBHOOK_URL?.startsWith("https://")
  ) {
    return reply(500, { error: "server_configuration_error" });
  }

  const declaredLength = Number(request.headers.get("content-length") ?? 0);
  if (declaredLength > MAX_BODY_BYTES) {
    return reply(413, { error: "body_too_large" });
  }

  const rawBody = await request.text();
  if (new TextEncoder().encode(rawBody).byteLength > MAX_BODY_BYTES) {
    return reply(413, { error: "body_too_large" });
  }

  let envelope;
  try {
    envelope = JSON.parse(rawBody);
  } catch {
    return reply(400, { error: "invalid_json" });
  }

  if (envelope.subscription !== CLOUD_BUILD_SUBSCRIPTION) {
    return reply(400, { error: "unexpected_subscription" });
  }

  const message = envelope.message;
  const messageId = message?.messageId ?? message?.message_id;
  const buildId = message?.attributes?.buildId;
  const status = message?.attributes?.status;

  if (!SAFE_ID.test(messageId ?? "") || !SAFE_ID.test(buildId ?? "")) {
    return reply(400, { error: "invalid_message" });
  }

  if (status !== "SUCCESS") return reply(204);

  const build = decodeBase64Json(message.data);
  if (
    !build ||
    build.status !== "SUCCESS" ||
    build.id !== buildId ||
    build.buildTriggerId !== CLOUD_BUILD_TRIGGER_ID
  ) {
    if (
      build?.buildTriggerId &&
      build.buildTriggerId !== CLOUD_BUILD_TRIGGER_ID
    ) {
      return reply(204);
    }
    return reply(400, { error: "inconsistent_build_event" });
  }

  const pingBody = JSON.stringify({
    action: null,
    title: "Cloud build succeeded",
    message: "Selected Google Cloud Build finished successfully",
  });

  let downstream;
  try {
    downstream = await fetchImpl(PINGROOM_WEBHOOK_URL, {
      method: "POST",
      redirect: "manual",
      signal: AbortSignal.timeout(8_000),
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        "Idempotency-Key": `gcb:${messageId}`,
      },
      body: pingBody,
    });
  } catch {
    return reply(503, { error: "pingroom_unavailable" });
  }

  if (!downstream.ok) {
    return reply(503, { error: "pingroom_rejected" });
  }

  return reply(204);
}
```

The body sent to PingRoom is fixed. It contains no repository, branch, commit, trigger name, substitutions, log URL, actor, or build ID. The Pub/Sub message ID appears only in the `Idempotency-Key` header so an exact redelivery maps to the original accepted request.

Returning `204` for unrelated statuses and triggers acknowledges those messages without creating a Ping. Returning `503` when PingRoom is unavailable tells [Pub/Sub push delivery](https://docs.cloud.google.com/pubsub/docs/push) to retry. Keep a dead-letter policy and logs for repeated invalid events; a phone alert is not the durable event record.

## Create an authenticated push subscription

Cloud Build publishes updates to the `cloud-builds` topic when that topic exists and the build configuration does not select another notification topic. Use a separate topic from any Pub/Sub-triggered build input. Google warns that reusing one topic for both directions can create a notification loop.

Create the notifications topic once if it does not already exist:

```bash
gcloud pubsub topics create cloud-builds
```

After deploying the private receiver, create a user-managed service account for push identity, grant it Cloud Run Invoker on only this service, and let the Pub/Sub service agent mint its ID token. Then create the push subscription:

```bash
gcloud pubsub subscriptions create pingroom-build-success \
  --topic=cloud-builds \
  --push-endpoint="$SERVICE_URL/cloud-build" \
  --push-auth-service-account="$PUSH_SERVICE_ACCOUNT"
```

Set `CLOUD_BUILD_SUBSCRIPTION` to the full name returned for that subscription. Set `CLOUD_BUILD_TRIGGER_ID` from the selected trigger's actual ID, not its display name.

The person creating the subscription needs permission to attach the push service account. The Pub/Sub service agent needs Service Account Token Creator, and the push service account needs Cloud Run Invoker. Follow Google's [authenticated push guide](https://docs.cloud.google.com/pubsub/docs/authenticate-push-subscriptions) and [Cloud Run tutorial](https://docs.cloud.google.com/run/docs/tutorials/pubsub) for the current IAM commands rather than widening roles to make an error disappear.

## Test nine boundaries

Use a harmless trigger and a test Room first:

1. A successful build from the selected trigger produces one Ping with the fixed title and message.
2. The same Pub/Sub message delivered twice creates no changed or duplicate content.
3. A failed or cancelled build creates no success Ping.
4. A success from another trigger creates no Ping.
5. An envelope whose build IDs disagree is rejected.
6. Malformed and oversized bodies are rejected before forwarding.
7. An unauthenticated caller is rejected by Cloud Run before the handler runs.
8. A redirect or non-success response from PingRoom is treated as a failed delivery.
9. The final phone preview contains no source, branch, commit, actor, substitution, or log data.

For this article, the exact handler passed nine controlled cases against Google Cloud-shaped fixtures and a stateful local mock of PingRoom's documented contract. The check accepted one target success, replayed it without a second Ping, ignored a failure and another trigger, and rejected inconsistent, malformed, oversized, redirected, and downstream-failed paths. It used no live Google Cloud project, PingRoom Room, phone, or network request.

![Two PingRoom avatars compare one accepted clay build token with its matching replay at a narrow receiver gate while Pingo watches beside a closed credential capsule.](/blog/google-cloud-build-success-push-notifications-1.webp)

[Pingo the Pigeon](/Pingo), PingRoom's fictional Chief Marketing Officer, is watching the duplicate token stop at the same gate. He has no Google Cloud account, private-Room access, webhook credential, or authority to send a Ping. People choose the trigger, service accounts, recipients, copy, and recovery policy.

_These AI-generated editorial illustrations feature approved PingRoom avatars and Pingo. They are not Google Cloud or PingRoom screenshots and do not depict customers._

## Keep Cloud Build authoritative

The Room should answer one question: did this selected build finish successfully? Logs, artifacts, approvals, retries, and the actual build state stay in Google Cloud. For broader event design, read [how to send CI/CD deployment notifications](/blog/ci-cd-deployment-notifications).

Open [PingRoom Rooms](/app/rooms), create the private destination, store its incoming-webhook URL in Secret Manager, and run one harmless successful build. Check Cloud Build history, the Pub/Sub delivery, the single Room event, and what a locked test phone displays before connecting an important project.

Google Cloud, Cloud Build, Cloud Run, Pub/Sub, and Secret Manager are trademarks of Google LLC. PingRoom is not affiliated with or endorsed by Google.
