Main Site ↗

google-chat-messages

by jezweb67441GitHub

Send Google Chat messages via webhook — text, rich cards (cardsV2), threaded replies. Includes TypeScript types, card builder utility, and widget reference.

Unlock Deep Analysis

Use AI to visualize the workflow and generate a realistic output preview for this skill.

Powered by Fastest LLM

Development
Compatible Agents
Claude Code
Claude Code
~/.claude/skills/
Codex CLI
Codex CLI
~/.codex/skills/
Gemini CLI
Gemini CLI
~/.gemini/skills/
O
OpenCode
~/.opencode/skills/
O
OpenClaw
~/.openclaw/skills/
GitHub Copilot
GitHub Copilot
~/.copilot/skills/
Cursor
Cursor
~/.cursor/skills/
W
Windsurf
~/.codeium/windsurf/skills/
C
Cline
~/.cline/skills/
R
Roo Code
~/.roo/skills/
K
Kiro
~/.kiro/skills/
J
Junie
~/.junie/skills/
A
Augment Code
~/.augment/skills/
W
Warp
~/.warp/skills/
G
Goose
~/.config/goose/skills/
SKILL.md

Google Chat Messages

Send messages to Google Chat spaces via incoming webhooks. Produces text messages, rich cards (cardsV2), and threaded replies.

What You Produce

  • Text messages with Google Chat formatting
  • Rich card messages (cardsV2) with headers, sections, widgets
  • Threaded conversations
  • Reusable webhook sender utility

Workflow

Step 1: Get Webhook URL

In Google Chat:

  1. Open a Space > click space name > Manage webhooks
  2. Create webhook (name it, optionally add avatar URL)
  3. Copy the webhook URL

Store the URL as an environment variable or in your secrets manager — never hardcode.

Step 2: Choose Message Type

NeedTypeComplexity
Simple notificationText messageLow
Structured info (status, digest)Card message (cardsV2)Medium
Ongoing updatesThreaded repliesMedium
Action buttons (open URL)Card with buttonListMedium

Step 3: Send the Message

Use assets/webhook-sender.ts for the sender utility. Use assets/card-builder.ts for structured card construction.

Text Formatting

Google Chat does NOT use standard Markdown.

FormatSyntaxExample
Bold*text**important*
Italic_text__emphasis_
Strikethrough~text~~removed~
Monospace`text``code`
Code block```text```Multi-line code
Link<url|text><https://example.com|Click here>
Mention user<users/USER_ID><users/123456>
Mention all<users/all><users/all>

Not supported: **double asterisks**, headings (###), blockquotes, tables, images inline.

Text Message Example

await sendText(webhookUrl, '*Build Complete*\n\nBranch: `main`\nStatus: Passed\n<https://ci.example.com/123|View Build>');

cardsV2 Structure

Cards use the cardsV2 format (recommended over legacy cards).

const message = {
  cardsV2: [{
    cardId: 'unique-id',
    card: {
      header: {
        title: 'Card Title',
        subtitle: 'Optional subtitle',
        imageUrl: 'https://example.com/icon.png',
        imageType: 'CIRCLE'  // or 'SQUARE'
      },
      sections: [{
        header: 'Section Title',  // optional
        widgets: [
          // widgets go here
        ]
      }]
    }
  }]
};

Widget Types

Text paragraph — formatted text block:

{ textParagraph: { text: '*Bold* and _italic_ text' } }

Decorated text — label + value with optional icon:

{
  decoratedText: {
    topLabel: 'Status',
    text: 'Deployed',
    startIcon: { knownIcon: 'STAR' }
  }
}

Button list — action buttons:

{
  buttonList: {
    buttons: [{
      text: 'View Dashboard',
      onClick: { openLink: { url: 'https://dashboard.example.com' } }
    }]
  }
}

Image — standalone image:

{ image: { imageUrl: 'https://example.com/chart.png', altText: 'Usage chart' } }

Divider — horizontal separator:

{ divider: {} }

See references/widget-reference.md for all widget types with full examples. See references/icon-list.md for all available knownIcon values.

Threading

Thread messages together using threadKey:

// First message — creates thread
const response = await sendCard(webhookUrl, card, {
  threadKey: 'deploy-2026-02-16'
});

// Reply to thread — append &messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD
const threadUrl = `${webhookUrl}&messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD`;
await sendCard(threadUrl, replyCard, {
  threadKey: 'deploy-2026-02-16'
});

The threadKey is a client-assigned string. Use consistent keys for related messages (e.g., deploy-{date}, alert-{id}).

Common Patterns

Notification Card

import { buildCard, sendCard } from './assets/card-builder';
import { sendWebhook } from './assets/webhook-sender';

const card = buildCard({
  cardId: 'deploy-notification',
  title: 'Deployment Complete',
  subtitle: 'production - v2.1.0',
  imageUrl: 'https://example.com/your-icon.png',
  sections: [{
    widgets: [
      { decoratedText: { topLabel: 'Environment', text: 'Production' } },
      { decoratedText: { topLabel: 'Version', text: 'v2.1.0' } },
      { decoratedText: { topLabel: 'Status', text: '*Healthy*', startIcon: { knownIcon: 'STAR' } } },
      { buttonList: { buttons: [{ text: 'View Deployment', onClick: { openLink: { url: 'https://dash.example.com' } } }] } }
    ]
  }]
});

Digest Card (Weekly Summary)

const digest = buildCard({
  cardId: 'weekly-digest',
  title: 'Weekly Summary',
  subtitle: `${count} updates this week`,
  sections: [
    {
      header: 'Highlights',
      widgets: items.map(item => ({
        decoratedText: { text: item.title, bottomLabel: item.date }
      }))
    },
    {
      widgets: [{
        buttonList: {
          buttons: [{ text: 'View All', onClick: { openLink: { url: dashboardUrl } } }]
        }
      }]
    }
  ]
});

Simple Text Alert

await sendText(webhookUrl, `*Alert*: CPU usage above 90% on \`worker-prod-1\`\n<${alertUrl}|View Alert>`);

Error Prevention

MistakeFix
**bold** in textUse *bold* (single asterisks)
[text](url) linksUse <url|text> format
Missing cardsV2 wrapperWrap card in { cardsV2: [{ cardId, card }] }
Thread replies not threadingAppend &messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD to webhook URL
Webhook returns 400Check JSON structure — common issue is missing text or cardsV2 at top level
Card not showingEnsure sections has at least one widget

Asset Files

FilePurpose
assets/types.tsTypeScript type definitions for cardsV2
assets/card-builder.tsUtility to build card messages
assets/webhook-sender.tsPOST to webhook with error handling

Referenced Files

The following files are referenced in this skill and included for context.

assets/webhook-sender.ts

import type { WebhookMessage } from './types';

interface SendOptions {
  /** Thread key for threading messages together */
  threadKey?: string;
  /** If true, replies to existing thread or falls back to new thread */
  replyToThread?: boolean;
}

/**
 * Send a message to Google Chat via incoming webhook.
 *
 * @param webhookUrl - The webhook URL from Google Chat space settings
 * @param message - WebhookMessage (use buildCard or buildText to create)
 * @param options - Threading options
 *
 * @example
 *   // Simple text
 *   await sendWebhook(url, { text: '*Hello* from webhook' });
 *
 *   // Card message
 *   const card = buildCard({ cardId: 'test', title: 'Test', sections: [...] });
 *   await sendWebhook(url, card);
 *
 *   // Threaded reply
 *   await sendWebhook(url, card, { threadKey: 'deploy-123', replyToThread: true });
 */
export async function sendWebhook(
  webhookUrl: string,
  message: WebhookMessage,
  options?: SendOptions
): Promise<void> {
  let url = webhookUrl;

  // Add threading parameters to URL
  if (options?.replyToThread && options?.threadKey) {
    const separator = url.includes('?') ? '&' : '?';
    url += `${separator}messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD`;
  }

  // Add thread key to message body
  if (options?.threadKey) {
    message.thread = { threadKey: options.threadKey };
  }

  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(message),
  });

  if (!response.ok) {
    const body = await response.text();
    throw new Error(
      `Google Chat webhook error ${response.status}: ${body}`
    );
  }
}

/**
 * Convenience: send a text message.
 */
export async function sendText(
  webhookUrl: string,
  text: string,
  options?: SendOptions
): Promise<void> {
  await sendWebhook(webhookUrl, { text }, options);
}

assets/card-builder.ts

import type { CardWithId, CardHeader, CardSection, WebhookMessage, Thread } from './types';

interface BuildCardOptions {
  cardId: string;
  title: string;
  subtitle?: string;
  imageUrl?: string;
  imageType?: 'SQUARE' | 'CIRCLE';
  sections: CardSection[];
}

/**
 * Build a cardsV2 webhook message.
 *
 * Usage:
 *   const message = buildCard({ cardId: 'my-card', title: 'Hello', sections: [...] });
 *   await sendWebhook(webhookUrl, message);
 */
export function buildCard(
  options: BuildCardOptions,
  thread?: { threadKey: string }
): WebhookMessage {
  const header: CardHeader = {
    title: options.title,
    ...(options.subtitle && { subtitle: options.subtitle }),
    ...(options.imageUrl && { imageUrl: options.imageUrl }),
    ...(options.imageType && { imageType: options.imageType }),
  };

  const card: CardWithId = {
    cardId: options.cardId,
    card: {
      header,
      sections: options.sections,
    },
  };

  const message: WebhookMessage = {
    cardsV2: [card],
  };

  if (thread) {
    message.thread = { threadKey: thread.threadKey };
  }

  return message;
}

/**
 * Build a simple text webhook message.
 *
 * Remember: Google Chat uses *bold*, _italic_, ~strikethrough~, `code`,
 * and <url|text> for links. NOT standard Markdown.
 */
export function buildText(
  text: string,
  thread?: { threadKey: string }
): WebhookMessage {
  const message: WebhookMessage = { text };

  if (thread) {
    message.thread = { threadKey: thread.threadKey };
  }

  return message;
}

references/widget-reference.md

# Google Chat Widget Reference

All widget types available in cardsV2 sections.

## textParagraph

Formatted text block. Supports Google Chat formatting (`*bold*`, `_italic_`, `<url|text>`).

```typescript
{
  textParagraph: {
    text: '*Status*: All systems operational\n_Last checked_: 5 minutes ago'
  }
}

decoratedText

Labelled value with optional icons. Most versatile widget for key-value data.

Basic

{
  decoratedText: {
    topLabel: 'Environment',
    text: 'Production',
    bottomLabel: 'Last deployed 2h ago'
  }
}

With Start Icon

{
  decoratedText: {
    topLabel: 'Status',
    text: 'Healthy',
    startIcon: { knownIcon: 'STAR' }
  }
}

With Custom Icon URL

{
  decoratedText: {
    topLabel: 'GitHub',
    text: 'PR #142 merged',
    startIcon: {
      iconUrl: 'https://github.githubassets.com/favicons/favicon.svg',
      altText: 'GitHub'
    }
  }
}

With Button

{
  decoratedText: {
    topLabel: 'Alert',
    text: 'CPU at 95%',
    button: {
      text: 'View',
      onClick: { openLink: { url: 'https://monitoring.example.com' } }
    }
  }
}

Clickable (Whole Widget)

{
  decoratedText: {
    text: 'View full report',
    wrapText: true,
    onClick: { openLink: { url: 'https://reports.example.com' } }
  }
}

With Wrap Text

{
  decoratedText: {
    topLabel: 'Description',
    text: 'This is a longer description that should wrap to multiple lines instead of being truncated',
    wrapText: true
  }
}

buttonList

One or more action buttons. Buttons open URLs or trigger actions.

Single Button

{
  buttonList: {
    buttons: [{
      text: 'Open Dashboard',
      onClick: { openLink: { url: 'https://dashboard.example.com' } }
    }]
  }
}

Multiple Buttons

{
  buttonList: {
    buttons: [
      {
        text: 'Approve',
        onClick: { openLink: { url: 'https://app.example.com/approve/123' } },
        color: { red: 0, green: 0.5, blue: 0, alpha: 1 }
      },
      {
        text: 'Reject',
        onClick: { openLink: { url: 'https://app.example.com/reject/123' } }
      }
    ]
  }
}

Button with Icon

{
  buttonList: {
    buttons: [{
      text: 'View on GitHub',
      icon: { knownIcon: 'BOOKMARK' },
      onClick: { openLink: { url: 'https://github.com/org/repo/pull/42' } }
    }]
  }
}

image

Standalone image widget.

{
  image: {
    imageUrl: 'https://example.com/chart.png',
    altText: 'Monthly usage chart'
  }
}

divider

Horizontal line separator between widgets.

{ divider: {} }

Collapsible Sections

Sections can be collapsed with only the first N widgets visible:

{
  header: 'Details',
  collapsible: true,
  uncollapsibleWidgetsCount: 2,  // Show first 2, collapse rest
  widgets: [
    // First 2 always visible
    { decoratedText: { topLabel: 'Status', text: 'Active' } },
    { decoratedText: { topLabel: 'Region', text: 'AU' } },
    // These start collapsed
    { decoratedText: { topLabel: 'Instance', text: 'prod-01' } },
    { decoratedText: { topLabel: 'Memory', text: '2.1 GB' } },
    { decoratedText: { topLabel: 'CPU', text: '45%' } }
  ]
}

Combining Widgets

A typical card section combining multiple widget types:

{
  header: 'Deployment Summary',
  widgets: [
    { textParagraph: { text: '*Production deployment complete*' } },
    { divider: {} },
    { decoratedText: { topLabel: 'Version', text: 'v2.3.1', startIcon: { knownIcon: 'STAR' } } },
    { decoratedText: { topLabel: 'Duration', text: '2m 34s', startIcon: { knownIcon: 'CLOCK' } } },
    { decoratedText: { topLabel: 'Commit', text: '`abc1234` Fix auth redirect' } },
    { divider: {} },
    { buttonList: { buttons: [
      { text: 'View Logs', onClick: { openLink: { url: 'https://logs.example.com' } } },
      { text: 'Rollback', onClick: { openLink: { url: 'https://deploy.example.com/rollback' } } }
    ]}}
  ]
}

### references/icon-list.md

```markdown
# Google Chat Known Icons

Icons available via `knownIcon` in decoratedText and button widgets.

## Usage

```typescript
{ startIcon: { knownIcon: 'STAR' } }
// or
{ icon: { knownIcon: 'EMAIL' } }

Available Icons

Icon NameUse For
AIRPLANETravel, flights
BOOKMARKSave, reference, links
BUSTransport, transit
CARDriving, transport
CLOCKTime, duration, schedule
CONFIRMATION_NUMBER_ICONTickets, bookings
DESCRIPTIONDocuments, files
DOLLARMoney, pricing, cost
EMAILEmail, messages
EVENT_PERFORMERPeople, performers
EVENT_SEATSeating, capacity
FLIGHT_ARRIVALArrivals, incoming
FLIGHT_DEPARTUREDepartures, outgoing
HOTELAccommodation
HOTEL_ROOM_TYPERoom types
INVITEInvitations
MAP_PINLocation, address
MEMBERSHIPMembers, users
MULTIPLE_PEOPLETeams, groups
OFFERDeals, promotions
PERSONIndividual user
PHONEPhone number, calls
RESTAURANT_ICONFood, dining
SHOPPING_CARTCommerce, purchases
STARRating, favourite, important
STOREShop, retail
TICKETTickets, events
TRAINRail transport
VIDEO_CAMERAVideo, meetings
VIDEO_PLAYPlay, media

Custom Icons

For icons not in the list, use iconUrl with any publicly accessible image:

{
  startIcon: {
    iconUrl: 'https://example.com/custom-icon.png',
    altText: 'Custom icon description'
  }
}

Icon images should be square, ideally 24x24 or 48x48 pixels.

Material Design Icons

Google Chat's knownIcons are a subset of Material Design icons. For a broader selection, use iconUrl pointing to Material Design icon SVGs or your own icon assets.


### assets/types.ts

```typescript
// Google Chat cardsV2 TypeScript types for webhook messages
// Reference: https://developers.google.com/workspace/chat/api/reference/rest/v1/spaces.messages

export interface WebhookMessage {
  text?: string;
  cardsV2?: CardWithId[];
  thread?: Thread;
}

export interface Thread {
  threadKey?: string;
}

export interface CardWithId {
  cardId: string;
  card: Card;
}

export interface Card {
  header?: CardHeader;
  sections: CardSection[];
}

export interface CardHeader {
  title: string;
  subtitle?: string;
  imageUrl?: string;
  imageType?: 'SQUARE' | 'CIRCLE';
}

export interface CardSection {
  header?: string;
  widgets: Widget[];
  collapsible?: boolean;
  uncollapsibleWidgetsCount?: number;
}

export interface Widget {
  textParagraph?: { text: string };
  image?: { imageUrl: string; altText?: string };
  decoratedText?: DecoratedText;
  buttonList?: { buttons: Button[] };
  divider?: Record<string, never>;
}

export interface DecoratedText {
  topLabel?: string;
  text: string;
  bottomLabel?: string;
  startIcon?: Icon;
  endIcon?: Icon;
  wrapText?: boolean;
  onClick?: OnClick;
  button?: Button;
}

export interface Icon {
  knownIcon?: string;
  iconUrl?: string;
  altText?: string;
}

export interface Button {
  text: string;
  icon?: Icon;
  color?: { red?: number; green?: number; blue?: number; alpha?: number };
  onClick: OnClick;
  disabled?: boolean;
  altText?: string;
}

export interface OnClick {
  openLink?: { url: string };
  action?: {
    function?: string;
    parameters?: { key: string; value: string }[];
  };
}

Source: https://github.com/jezweb/claude-skills#plugins-integrations-skills-google-chat-messages

Content curated from original sources, copyright belongs to authors

Grade B
-AI Score
Best Practices
Checking...
Try this Skill

User Rating

USER RATING

0UP
0DOWN
Loading files...

WORKS WITH

Claude Code
Claude
Codex CLI
Codex
Gemini CLI
Gemini
O
OpenCode
O
OpenClaw
GitHub Copilot
Copilot
Cursor
Cursor
W
Windsurf
C
Cline
R
Roo
K
Kiro
J
Junie
A
Augment
W
Warp
G
Goose