google-chat-api by jezweb/claude-skills
npx skills add https://github.com/jezweb/claude-skills --skill google-chat-api状态:生产就绪 最后更新:2026-01-09(新增:Spaces API、Members API、Reactions API、速率限制) 依赖项:Cloudflare Workers(推荐)、用于令牌验证的 Web Crypto API 最新版本:Google Chat API v1(稳定版)、Cards v2(Cards v1 已弃用)、wrangler@4.54.0
# 无需代码 - 只需在 Google Chat 中配置
# 1. 前往 Google Cloud Console
# 2. 创建新项目或选择现有项目
# 3. 启用 Google Chat API
# 4. 使用 webhook URL 配置 Chat 应用
Webhook URL:https://your-worker.workers.dev/webhook
为什么这很重要:
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const event = await request.json()
// 使用卡片进行响应
return Response.json({
text: "Hello from bot!",
cardsV2: [{
cardId: "unique-card-1",
card: {
header: { title: "Welcome" },
sections: [{
widgets: [{
textParagraph: { text: "Click the button below" }
}, {
buttonList: {
buttons: [{
text: "Click me",
onClick: {
action: {
function: "handleClick",
parameters: [{ key: "data", value: "test" }]
}
}
}]
}
}]
}]
}
}]
})
}
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
关键点:
cardsV2 数组的有效 JSONasync function verifyToken(token: string): Promise<boolean> {
// 验证令牌是否由 chat@system.gserviceaccount.com 签名
// 完整实现请参见 templates/bearer-token-verify.ts
return true
}
为什么这很重要:
选项 A:传入 Webhook(仅通知)
最适合:
设置:
无需代码 - 只需 HTTP POST:
curl -X POST 'https://chat.googleapis.com/v1/spaces/.../messages?key=...' \
-H 'Content-Type: application/json' \
-d '{"text": "Hello from webhook!"}'
选项 B:HTTP 端点机器人(交互式)
最适合:
设置:
需要代码 - 参见 templates/interactive-bot.ts
重要:仅使用 Cards v2。Cards v1 已于 2025 年弃用。Cards v2 与网页上的 Material Design 匹配(渲染更快,美学效果更好)。
Cards v2 结构:
{
"cardsV2": [{
"cardId": "unique-id",
"card": {
"header": {
"title": "Card Title",
"subtitle": "Optional subtitle",
"imageUrl": "https://..."
},
"sections": [{
"header": "Section 1",
"widgets": [
{ "textParagraph": { "text": "Some text" } },
{ "buttonList": { "buttons": [...] } }
]
}]
}
}]
}
组件类型:
textParagraph - 文本内容buttonList - 按钮(文本或图标)textInput - 文本输入字段selectionInput - 下拉列表、复选框、开关dateTimePicker - 日期/时间选择divider - 水平线image - 图像decoratedText - 带有图标/按钮的文本文本格式化(新增:2025年9月 - 正式发布):
Cards v2 同时支持 HTML 和 Markdown 格式化:
// HTML 格式化(传统方式)
{
textParagraph: {
text: "This is <b>bold</b> and <i>italic</i> text with <font color='#ea9999'>color</font>"
}
}
// Markdown 格式化(新增 - 更适合 AI 代理)
{
textParagraph: {
text: "This is **bold** and *italic* text\n\n- Bullet list\n- Second item\n\n```\ncode block\n```"
}
}
支持的 Markdown(文本消息和卡片):
**bold** 或 *italic*code 用于行内代码- list item 或 1. ordered 用于列表code block 用于多行代码~strikethrough~支持的 HTML(仅卡片):
<b>bold</b>、<i>italic</i>、<u>underline</u><font color="#FF0000">colored</font><a href="url">link</a>为什么 Markdown 很重要:LLM 自然输出 Markdown。在 2025 年 9 月之前,你必须将 Markdown 转换为 HTML。现在你可以直接将 Markdown 传递给 Chat。
关键点:
当用户点击按钮或提交表单时:
export default {
async fetch(request: Request): Promise<Response> {
const event = await request.json()
// 检查事件类型
if (event.type === 'MESSAGE') {
// 用户发送了消息
return handleMessage(event)
}
if (event.type === 'CARD_CLICKED') {
// 用户点击了按钮
const action = event.action.actionMethodName
const params = event.action.parameters
if (action === 'submitForm') {
return handleFormSubmission(event)
}
}
return Response.json({ text: "Unknown event" })
}
}
事件类型:
ADDED_TO_SPACE - 机器人被添加到空间REMOVED_FROM_SPACE - 机器人被移除MESSAGE - 用户发送消息CARD_CLICKED - 用户点击按钮/提交表单✅ 返回包含 cardsV2 数组结构的有效 JSON
✅ 为每个卡片设置唯一的 cardId
✅ 为 HTTP 端点验证 bearer 令牌(生产环境)
✅ 处理所有事件类型(MESSAGE、CARD_CLICKED 等)
✅ 保持每个卡片的组件数量少于 100
✅ 在服务器端验证表单输入
❌ 在代码中存储密钥(使用 Cloudflare Workers 密钥) ❌ 每个卡片超过 100 个组件(会静默失败) ❌ 返回格式错误的 JSON(会破坏整个消息) ❌ 跳过 bearer 令牌验证(安全风险) ❌ 仅信任客户端验证(必须在服务器端验证) ❌ 使用同步阻塞操作(超时风险)
此技能可预防 6 个已记录的问题:
错误:"Unauthorized" 或 "Invalid credentials" 来源:Google Chat API 文档 发生原因:令牌未验证或验证方法错误 预防:模板包含 Web Crypto API 验证(兼容 Cloudflare Workers)
错误:"Invalid JSON payload" 或 "Unknown field"
来源:Cards v2 API 参考
发生原因:字段名拼写错误、嵌套错误或额外字段
预防:使用 google-chat-cards 库或具有精确模式的模板
错误:无错误 - 超过 100 个的组件不会渲染 来源:Google Chat API 限制 发生原因:向单个卡片添加了太多组件 预防:技能文档记录了 100 个组件的限制 + 分页模式
错误:表单未向用户显示验证错误 来源:交互式卡片文档 发生原因:错误的错误响应格式 预防:模板包含正确的错误格式:
{
"actionResponse": {
"type": "DIALOG",
"dialogAction": {
"actionStatus": {
"statusCode": "INVALID_ARGUMENT",
"userFacingMessage": "Email is required"
}
}
}
}
错误:Chat 显示 "无法连接到机器人" 来源:Webhook 设置指南 发生原因:URL 无法公开访问、超时或响应格式错误 预防:技能包含超时处理 + 响应格式验证
错误:"RESOURCE_EXHAUSTED" 或 429 状态码 来源:Google Chat API 配额 发生原因:超出每个项目、每个空间或每个用户的请求限制 预防:技能记录了速率限制 + 指数退避模式
{
"name": "google-chat-bot",
"main": "src/index.ts",
"compatibility_date": "2026-01-03",
"compatibility_flags": ["nodejs_compat"],
// 密钥(使用以下命令设置:wrangler secret put CHAT_BOT_TOKEN)
"vars": {
"ALLOWED_SPACES": "spaces/SPACE_ID_1,spaces/SPACE_ID_2"
}
}
为什么需要这些设置:
nodejs_compat - Web Crypto API(令牌验证)必需// 外部服务向 Chat 发送通知
async function sendNotification(webhookUrl: string, message: string) {
await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: message,
cardsV2: [{
cardId: `notif-${Date.now()}`,
card: {
header: { title: "Alert" },
sections: [{
widgets: [{
textParagraph: { text: message }
}]
}]
}
}]
})
})
}
何时使用:CI/CD 警报、监控通知、事件触发器
// 显示表单以收集数据
function showForm() {
return {
cardsV2: [{
cardId: "form-card",
card: {
header: { title: "Enter Details" },
sections: [{
widgets: [
{
textInput: {
name: "email",
label: "Email",
type: "SINGLE_LINE",
hintText: "user@example.com"
}
},
{
selectionInput: {
name: "priority",
label: "Priority",
type: "DROPDOWN",
items: [
{ text: "Low", value: "low" },
{ text: "High", value: "high" }
]
}
},
{
buttonList: {
buttons: [{
text: "Submit",
onClick: {
action: {
function: "submitForm",
parameters: [{
key: "formId",
value: "contact-form"
}]
}
}
}]
}
}
]
}]
}
}]
}
}
何时使用:数据收集、审批工作流、工单创建
// 打开模态对话框
function openDialog() {
return {
actionResponse: {
type: "DIALOG",
dialogAction: {
dialog: {
body: {
sections: [{
header: "Confirm Action",
widgets: [{
textParagraph: { text: "Are you sure?" }
}, {
buttonList: {
buttons: [
{
text: "Confirm",
onClick: {
action: { function: "confirm" }
}
},
{
text: "Cancel",
onClick: {
action: { function: "cancel" }
}
}
]
}
}]
}]
}
}
}
}
}
}
何时使用:确认、多步骤工作流、聚焦数据输入
此技能没有可执行脚本。
所有项目必需:
templates/webhook-handler.ts - 基本 webhook 接收器templates/wrangler.jsonc - Cloudflare Workers 配置根据需求可选:
templates/interactive-bot.ts - 带有事件处理的 HTTP 端点templates/card-builder-examples.ts - 常见卡片模式templates/form-validation.ts - 带有错误响应的输入验证templates/bearer-token-verify.ts - 令牌验证工具何时加载这些:当用户要求以下操作时,Claude 应参考模板:
references/google-chat-docs.md - 关键文档链接references/cards-v2-schema.md - 完整的卡片结构参考references/common-errors.md - 错误故障排除指南Claude 何时应加载这些:故障排除错误、设计卡片、理解 API
注册斜杠命令以进行快速操作:
// 用户输入:/create-ticket Bug in login
if (event.message?.slashCommand?.commandName === 'create-ticket') {
const text = event.message.argumentText
return Response.json({
text: `Creating ticket: ${text}`,
cardsV2: [/* 工单确认卡片 */]
})
}
使用场景:快速操作、快捷方式、高级用户功能
在现有线程中回复:
return Response.json({
text: "Reply in thread",
thread: {
name: event.message.thread.name // 使用现有线程
}
})
使用场景:对话、后续跟进、分组讨论
以编程方式管理 Google Chat 空间(聊天室)。需要 Chat 管理员或应用权限。
| 方法 | 描述 | 所需作用域 |
|---|---|---|
spaces.create | 创建新空间 | chat.spaces.create |
spaces.delete | 删除空间 | chat.delete |
spaces.get | 获取空间详情 | chat.spaces.readonly |
spaces.list | 列出机器人所在的空间 | chat.spaces.readonly |
spaces.patch | 更新空间设置 | chat.spaces |
spaces.search | 按条件搜索空间 | chat.spaces.readonly |
spaces.setup | 创建空间并添加成员 | chat.spaces.create |
spaces.findDirectMessage | 查找与特定用户的私聊 | chat.spaces.readonly |
async function createSpace(accessToken: string) {
const response = await fetch('https://chat.googleapis.com/v1/spaces', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
spaceType: 'SPACE', // 或 'GROUP_CHAT', 'DIRECT_MESSAGE'
displayName: 'Project Team',
singleUserBotDm: false,
spaceDetails: {
description: 'Team collaboration space',
guidelines: 'Be respectful and on-topic'
}
})
})
return response.json()
}
async function listSpaces(accessToken: string) {
const response = await fetch(
'https://chat.googleapis.com/v1/spaces?pageSize=100',
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
)
const data = await response.json()
// 返回:{ spaces: [...], nextPageToken: '...' }
return data.spaces
}
async function searchSpaces(accessToken: string, query: string) {
const params = new URLSearchParams({
query: query, // 例如:'displayName:Project'
pageSize: '50'
})
const response = await fetch(
`https://chat.googleapis.com/v1/spaces:search?${params}`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
)
return response.json()
}
搜索查询语法:
displayName:Project - 名称包含 "Project"spaceType:SPACE - 仅空间(非私聊)createTime>2025-01-01 - 创建于指定日期之后AND/OR 运算符组合以编程方式管理空间成员资格。需要 用户或应用授权。
| 方法 | 描述 | 所需作用域 |
|---|---|---|
spaces.members.create | 向空间添加成员 | chat.memberships |
spaces.members.delete | 移除成员 | chat.memberships |
spaces.members.get | 获取成员详情 | chat.memberships.readonly |
spaces.members.list | 列出所有成员 | chat.memberships.readonly |
spaces.members.patch | 更新成员角色 | chat.memberships |
async function addMember(accessToken: string, spaceName: string, userEmail: string) {
const response = await fetch(
`https://chat.googleapis.com/v1/${spaceName}/members`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
member: {
name: `users/${userEmail}`,
type: 'HUMAN' // 或 'BOT'
},
role: 'ROLE_MEMBER' // 或 'ROLE_MANAGER'
})
}
)
return response.json()
}
async function listMembers(accessToken: string, spaceName: string) {
const response = await fetch(
`https://chat.googleapis.com/v1/${spaceName}/members?pageSize=100`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
)
return response.json()
// 返回:{ memberships: [...], nextPageToken: '...' }
}
async function updateMemberRole(
accessToken: string,
memberName: string, // 例如:'spaces/ABC/members/DEF'
newRole: 'ROLE_MEMBER' | 'ROLE_MANAGER'
) {
const response = await fetch(
`https://chat.googleapis.com/v1/${memberName}?updateMask=role`,
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ role: newRole })
}
)
return response.json()
}
成员角色:
ROLE_MEMBER - 标准成员(读/写消息)ROLE_MANAGER - 可以管理空间设置和成员向消息添加表情符号反应。2025 年新增,支持自定义工作区表情符号。
| 方法 | 描述 |
|---|---|
spaces.messages.reactions.create | 向消息添加反应 |
spaces.messages.reactions.delete | 移除反应 |
spaces.messages.reactions.list | 列出消息上的反应 |
async function addReaction(
accessToken: string,
messageName: string, // 例如:'spaces/ABC/messages/XYZ'
emoji: string
) {
const response = await fetch(
`https://chat.googleapis.com/v1/${messageName}/reactions`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
emoji: {
unicode: emoji // 例如:'👍' 或自定义表情符号代码
}
})
}
)
return response.json()
}
async function listReactions(accessToken: string, messageName: string) {
const response = await fetch(
`https://chat.googleapis.com/v1/${messageName}/reactions?pageSize=100`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
)
return response.json()
// 返回:{ reactions: [...], nextPageToken: '...' }
}
自定义表情符号:工作区管理员可以上传自定义表情符号。使用表情符号的 customEmoji.uid 而不是 unicode。
Google Chat API 强制执行严格的配额以防止滥用。了解这些限制对于生产应用至关重要。
| 操作 | 限制 | 备注 |
|---|---|---|
| 读取操作 | 3,000/分钟 | spaces.get、members.list、messages.list |
| 成员资格写入 | 300/分钟 | members.create、members.delete |
| 空间写入 | 60/分钟 | spaces.create、spaces.patch |
| 消息操作 | 600/分钟 | messages.create、reactions.create |
| 反应 | 600/分钟 | 与消息操作共享 |
| 操作 | 限制 |
|---|---|
| 读取操作 | 15/秒 |
| 写入操作 | 1/秒 |
用户身份验证的请求也会按用户进行限制:
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn()
} catch (error: any) {
if (error.status === 429) {
// 速率受限 - 使用指数退避等待
const waitMs = Math.pow(2, i) * 1000 + Math.random() * 1000
await new Promise(r => setTimeout(r, waitMs))
continue
}
throw error
}
}
throw new Error('Max retries exceeded')
}
// 用法
const spaces = await withRetry(() => listSpaces(accessToken))
最佳实践:
必需:
可选:
google-chat-cards@1.0.3 - 类型安全的卡片构建器(非官方){
"dependencies": {
"google-chat-cards": "^1.0.3"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20260109.0",
"wrangler": "^4.58.0"
}
}
注意:没有官方的 Google Chat npm 包 - 直接使用 fetch API。
此技能基于真实世界的实现:
令牌节省:约 65-70%(8k → 2.5k 令牌) 预防的错误:6/6 个已记录的问题 验证:✅ Webhook 处理器、✅ 卡片构建器、✅ 令牌验证、✅ 表单验证、✅ 速率限制处理
解决方案:实现 bearer 令牌验证(参见 templates/bearer-token-verify.ts)
解决方案:根据 Cards v2 模式验证卡片 JSON,确保字段名完全正确
解决方案:拆分为多个卡片或使用分页
解决方案:返回带有 actionResponse.dialogAction.actionStatus 的正确错误格式
解决方案:确保 URL 可公开访问、在超时时间内响应、返回有效的 JSON
使用此检查清单验证你的设置:
有问题?遇到问题?
references/common-errors.md 进行故障排除每周安装次数
313
代码仓库
GitHub 星标数
643
首次出现
Jan 20, 2026
安全审计
已安装于
claude-code261
gemini-cli212
opencode207
cursor198
antigravity192
codex181
Status : Production Ready Last Updated : 2026-01-09 (Added: Spaces API, Members API, Reactions API, Rate Limits) Dependencies : Cloudflare Workers (recommended), Web Crypto API for token verification Latest Versions : Google Chat API v1 (stable), Cards v2 (Cards v1 deprecated), wrangler@4.54.0
# No code needed - just configure in Google Chat
# 1. Go to Google Cloud Console
# 2. Create new project or select existing
# 3. Enable Google Chat API
# 4. Configure Chat app with webhook URL
Webhook URL : https://your-worker.workers.dev/webhook
Why this matters:
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const event = await request.json()
// Respond with a card
return Response.json({
text: "Hello from bot!",
cardsV2: [{
cardId: "unique-card-1",
card: {
header: { title: "Welcome" },
sections: [{
widgets: [{
textParagraph: { text: "Click the button below" }
}, {
buttonList: {
buttons: [{
text: "Click me",
onClick: {
action: {
function: "handleClick",
parameters: [{ key: "data", value: "test" }]
}
}
}]
}
}]
}]
}
}]
})
}
}
CRITICAL:
cardsV2 arrayasync function verifyToken(token: string): Promise<boolean> {
// Verify token is signed by chat@system.gserviceaccount.com
// See templates/bearer-token-verify.ts for full implementation
return true
}
Why this matters:
Option A: Incoming Webhook (Notifications Only)
Best for:
Setup :
No code required - just HTTP POST:
curl -X POST 'https://chat.googleapis.com/v1/spaces/.../messages?key=...' \
-H 'Content-Type: application/json' \
-d '{"text": "Hello from webhook!"}'
Option B: HTTP Endpoint Bot (Interactive)
Best for:
Setup :
Requires code - see templates/interactive-bot.ts
IMPORTANT : Use Cards v2 only. Cards v1 was deprecated in 2025. Cards v2 matches Material Design on web (faster rendering, better aesthetics).
Cards v2 structure:
{
"cardsV2": [{
"cardId": "unique-id",
"card": {
"header": {
"title": "Card Title",
"subtitle": "Optional subtitle",
"imageUrl": "https://..."
},
"sections": [{
"header": "Section 1",
"widgets": [
{ "textParagraph": { "text": "Some text" } },
{ "buttonList": { "buttons": [...] } }
]
}]
}
}]
}
Widget Types :
textParagraph - Text contentbuttonList - Buttons (text or icon)textInput - Text input fieldselectionInput - Dropdowns, checkboxes, switchesdateTimePicker - Date/time selectiondivider - Horizontal lineimage - ImagesdecoratedText - Text with icon/buttonText Formatting (NEW: Sept 2025 - GA):
Cards v2 supports both HTML and Markdown formatting:
// HTML formatting (traditional)
{
textParagraph: {
text: "This is <b>bold</b> and <i>italic</i> text with <font color='#ea9999'>color</font>"
}
}
// Markdown formatting (NEW - better for AI agents)
{
textParagraph: {
text: "This is **bold** and *italic* text\n\n- Bullet list\n- Second item\n\n```\ncode block\n```"
}
}
Supported Markdown (text messages and cards):
**bold** or *italic*code for inline code- list item or 1. ordered for listscode block for multi-line code~strikethrough~Supported HTML (cards only):
<b>bold</b>, <i>italic</i>, <u>underline</u><font color="#FF0000">colored</font><a href="url">link</a>Why Markdown matters : LLMs naturally output Markdown. Before Sept 2025, you had to convert Markdown→HTML. Now you can pass Markdown directly to Chat.
CRITICAL :
When user clicks button or submits form:
export default {
async fetch(request: Request): Promise<Response> {
const event = await request.json()
// Check event type
if (event.type === 'MESSAGE') {
// User sent message
return handleMessage(event)
}
if (event.type === 'CARD_CLICKED') {
// User clicked button
const action = event.action.actionMethodName
const params = event.action.parameters
if (action === 'submitForm') {
return handleFormSubmission(event)
}
}
return Response.json({ text: "Unknown event" })
}
}
Event Types :
ADDED_TO_SPACE - Bot added to spaceREMOVED_FROM_SPACE - Bot removedMESSAGE - User sent messageCARD_CLICKED - User clicked button/submitted form✅ Return valid JSON with cardsV2 array structure ✅ Set unique cardId for each card ✅ Verify bearer tokens for HTTP endpoints (production) ✅ Handle all event types (MESSAGE, CARD_CLICKED, etc.) ✅ Keep widget count under 100 per card ✅ Validate form inputs server-side
❌ Store secrets in code (use Cloudflare Workers secrets) ❌ Exceed 100 widgets per card (silently fails) ❌ Return malformed JSON (breaks entire message) ❌ Skip bearer token verification (security risk) ❌ Trust client-side validation only (validate server-side) ❌ Use synchronous blocking operations (timeout risk)
This skill prevents 6 documented issues:
Error : "Unauthorized" or "Invalid credentials" Source : Google Chat API Documentation Why It Happens : Token not verified or wrong verification method Prevention : Template includes Web Crypto API verification (Cloudflare Workers compatible)
Error : "Invalid JSON payload" or "Unknown field" Source : Cards v2 API Reference Why It Happens : Typo in field name, wrong nesting, or extra fields Prevention : Use google-chat-cards library or templates with exact schema
Error : No error - widgets beyond 100 simply don't render Source : Google Chat API Limits Why It Happens : Adding too many widgets to single card Prevention : Skill documents 100 widget limit + pagination patterns
Error : Form doesn't show validation errors to user Source : Interactive Cards Documentation Why It Happens : Wrong error response format Prevention : Templates include correct error format:
{
"actionResponse": {
"type": "DIALOG",
"dialogAction": {
"actionStatus": {
"statusCode": "INVALID_ARGUMENT",
"userFacingMessage": "Email is required"
}
}
}
}
Error : Chat shows "Unable to connect to bot" Source : Webhook Setup Guide Why It Happens : URL not publicly accessible, timeout, or wrong response format Prevention : Skill includes timeout handling + response format validation
Error : "RESOURCE_EXHAUSTED" or 429 status code Source : Google Chat API Quotas Why It Happens : Exceeding per-project, per-space, or per-user request limits Prevention : Skill documents rate limits + exponential backoff pattern
{
"name": "google-chat-bot",
"main": "src/index.ts",
"compatibility_date": "2026-01-03",
"compatibility_flags": ["nodejs_compat"],
// Secrets (set with: wrangler secret put CHAT_BOT_TOKEN)
"vars": {
"ALLOWED_SPACES": "spaces/SPACE_ID_1,spaces/SPACE_ID_2"
}
}
Why these settings:
nodejs_compat - Required for Web Crypto API (token verification)// External service sends notification to Chat
async function sendNotification(webhookUrl: string, message: string) {
await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: message,
cardsV2: [{
cardId: `notif-${Date.now()}`,
card: {
header: { title: "Alert" },
sections: [{
widgets: [{
textParagraph: { text: message }
}]
}]
}
}]
})
})
}
When to use : CI/CD alerts, monitoring notifications, event triggers
// Show form to collect data
function showForm() {
return {
cardsV2: [{
cardId: "form-card",
card: {
header: { title: "Enter Details" },
sections: [{
widgets: [
{
textInput: {
name: "email",
label: "Email",
type: "SINGLE_LINE",
hintText: "user@example.com"
}
},
{
selectionInput: {
name: "priority",
label: "Priority",
type: "DROPDOWN",
items: [
{ text: "Low", value: "low" },
{ text: "High", value: "high" }
]
}
},
{
buttonList: {
buttons: [{
text: "Submit",
onClick: {
action: {
function: "submitForm",
parameters: [{
key: "formId",
value: "contact-form"
}]
}
}
}]
}
}
]
}]
}
}]
}
}
When to use : Data collection, approval workflows, ticket creation
// Open modal dialog
function openDialog() {
return {
actionResponse: {
type: "DIALOG",
dialogAction: {
dialog: {
body: {
sections: [{
header: "Confirm Action",
widgets: [{
textParagraph: { text: "Are you sure?" }
}, {
buttonList: {
buttons: [
{
text: "Confirm",
onClick: {
action: { function: "confirm" }
}
},
{
text: "Cancel",
onClick: {
action: { function: "cancel" }
}
}
]
}
}]
}]
}
}
}
}
}
}
When to use : Confirmations, multi-step workflows, focused data entry
No executable scripts for this skill.
Required for all projects:
templates/webhook-handler.ts - Basic webhook receivertemplates/wrangler.jsonc - Cloudflare Workers configOptional based on needs:
templates/interactive-bot.ts - HTTP endpoint with event handlingtemplates/card-builder-examples.ts - Common card patternstemplates/form-validation.ts - Input validation with error responsestemplates/bearer-token-verify.ts - Token verification utilityWhen to load these : Claude should reference templates when user asks to:
references/google-chat-docs.md - Key documentation linksreferences/cards-v2-schema.md - Complete card structure referencereferences/common-errors.md - Error troubleshooting guideWhen Claude should load these : Troubleshooting errors, designing cards, understanding API
Register slash commands for quick actions:
// User types: /create-ticket Bug in login
if (event.message?.slashCommand?.commandName === 'create-ticket') {
const text = event.message.argumentText
return Response.json({
text: `Creating ticket: ${text}`,
cardsV2: [/* ticket confirmation card */]
})
}
Use cases : Quick actions, shortcuts, power user features
Reply in existing thread:
return Response.json({
text: "Reply in thread",
thread: {
name: event.message.thread.name // Use existing thread
}
})
Use cases : Conversations, follow-ups, grouped discussions
Programmatically manage Google Chat spaces (rooms). Requires Chat Admin or App permissions.
| Method | Description | Scope Required |
|---|---|---|
spaces.create | Create new space | chat.spaces.create |
spaces.delete | Delete a space | chat.delete |
spaces.get | Get space details | chat.spaces.readonly |
spaces.list |
async function createSpace(accessToken: string) {
const response = await fetch('https://chat.googleapis.com/v1/spaces', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
spaceType: 'SPACE', // or 'GROUP_CHAT', 'DIRECT_MESSAGE'
displayName: 'Project Team',
singleUserBotDm: false,
spaceDetails: {
description: 'Team collaboration space',
guidelines: 'Be respectful and on-topic'
}
})
})
return response.json()
}
async function listSpaces(accessToken: string) {
const response = await fetch(
'https://chat.googleapis.com/v1/spaces?pageSize=100',
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
)
const data = await response.json()
// Returns: { spaces: [...], nextPageToken: '...' }
return data.spaces
}
async function searchSpaces(accessToken: string, query: string) {
const params = new URLSearchParams({
query: query, // e.g., 'displayName:Project'
pageSize: '50'
})
const response = await fetch(
`https://chat.googleapis.com/v1/spaces:search?${params}`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
)
return response.json()
}
Search Query Syntax :
displayName:Project - Name contains "Project"spaceType:SPACE - Only spaces (not DMs)createTime>2025-01-01 - Created after dateAND/OR operatorsManage space membership programmatically. Requires User or App authorization.
| Method | Description | Scope Required |
|---|---|---|
spaces.members.create | Add member to space | chat.memberships |
spaces.members.delete | Remove member | chat.memberships |
spaces.members.get | Get member details | chat.memberships.readonly |
spaces.members.list |
async function addMember(accessToken: string, spaceName: string, userEmail: string) {
const response = await fetch(
`https://chat.googleapis.com/v1/${spaceName}/members`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
member: {
name: `users/${userEmail}`,
type: 'HUMAN' // or 'BOT'
},
role: 'ROLE_MEMBER' // or 'ROLE_MANAGER'
})
}
)
return response.json()
}
async function listMembers(accessToken: string, spaceName: string) {
const response = await fetch(
`https://chat.googleapis.com/v1/${spaceName}/members?pageSize=100`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
)
return response.json()
// Returns: { memberships: [...], nextPageToken: '...' }
}
async function updateMemberRole(
accessToken: string,
memberName: string, // e.g., 'spaces/ABC/members/DEF'
newRole: 'ROLE_MEMBER' | 'ROLE_MANAGER'
) {
const response = await fetch(
`https://chat.googleapis.com/v1/${memberName}?updateMask=role`,
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ role: newRole })
}
)
return response.json()
}
Member Roles :
ROLE_MEMBER - Standard member (read/write messages)ROLE_MANAGER - Can manage space settings and membersAdd emoji reactions to messages. Added in 2025, supports custom workspace emojis.
| Method | Description |
|---|---|
spaces.messages.reactions.create | Add reaction to message |
spaces.messages.reactions.delete | Remove reaction |
spaces.messages.reactions.list | List reactions on message |
async function addReaction(
accessToken: string,
messageName: string, // e.g., 'spaces/ABC/messages/XYZ'
emoji: string
) {
const response = await fetch(
`https://chat.googleapis.com/v1/${messageName}/reactions`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
emoji: {
unicode: emoji // e.g., '👍' or custom emoji code
}
})
}
)
return response.json()
}
async function listReactions(accessToken: string, messageName: string) {
const response = await fetch(
`https://chat.googleapis.com/v1/${messageName}/reactions?pageSize=100`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
)
return response.json()
// Returns: { reactions: [...], nextPageToken: '...' }
}
Custom Emoji : Workspace administrators can upload custom emoji. Use the emoji's customEmoji.uid instead of unicode.
Google Chat API enforces strict quotas to prevent abuse. Understanding these limits is critical for production apps.
| Operation | Limit | Notes |
|---|---|---|
| Read operations | 3,000/min | spaces.get, members.list, messages.list |
| Membership writes | 300/min | members.create, members.delete |
| Space writes | 60/min | spaces.create, spaces.patch |
| Message operations | 600/min | messages.create, reactions.create |
| Reactions | 600/min | Shared with message operations |
| Operation | Limit |
|---|---|
| Read operations | 15/sec |
| Write operations | 1/sec |
User-authenticated requests are also throttled per user:
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn()
} catch (error: any) {
if (error.status === 429) {
// Rate limited - wait with exponential backoff
const waitMs = Math.pow(2, i) * 1000 + Math.random() * 1000
await new Promise(r => setTimeout(r, waitMs))
continue
}
throw error
}
}
throw new Error('Max retries exceeded')
}
// Usage
const spaces = await withRetry(() => listSpaces(accessToken))
Best Practices :
Required :
Optional :
google-chat-cards@1.0.3 - Type-safe card builder (unofficial){
"dependencies": {
"google-chat-cards": "^1.0.3"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20260109.0",
"wrangler": "^4.58.0"
}
}
Note : No official Google Chat npm package - use fetch API directly.
This skill is based on real-world implementations:
Token Savings : ~65-70% (8k → 2.5k tokens) Errors Prevented : 6/6 documented issues Validation : ✅ Webhook handlers, ✅ Card builders, ✅ Token verification, ✅ Form validation, ✅ Rate limit handling
Solution : Implement bearer token verification (see templates/bearer-token-verify.ts)
Solution : Validate card JSON against Cards v2 schema, ensure exact field names
Solution : Split into multiple cards or use pagination
Solution : Return correct error format with actionResponse.dialogAction.actionStatus
Solution : Ensure URL is publicly accessible, responds within timeout, returns valid JSON
Use this checklist to verify your setup:
Questions? Issues?
references/common-errors.md for troubleshootingWeekly Installs
313
Repository
GitHub Stars
643
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
claude-code261
gemini-cli212
opencode207
cursor198
antigravity192
codex181
Azure 升级评估与自动化工具 - 轻松迁移 Functions 计划、托管层级和 SKU
66,100 周安装
| List spaces bot is in |
chat.spaces.readonly |
spaces.patch | Update space settings | chat.spaces |
spaces.search | Search spaces by criteria | chat.spaces.readonly |
spaces.setup | Create space and add members | chat.spaces.create |
spaces.findDirectMessage | Find DM with specific user | chat.spaces.readonly |
| List all members |
chat.memberships.readonly |
spaces.members.patch | Update member role | chat.memberships |