Monday.com ChatGPT Integration: Complete Automation Guide
Integrating Monday.com with ChatGPT apps unlocks powerful project management automation capabilities. This comprehensive guide provides production-ready code for building robust Monday.com integrations using the GraphQL API, webhooks, and advanced automation features.
Monday.com's GraphQL API offers comprehensive access to boards, items, columns, updates, and webhooks. Unlike traditional REST APIs, GraphQL enables precise data fetching with single queries, reducing network overhead and improving performance. When combined with ChatGPT's natural language capabilities, users can manage projects, track tasks, and automate workflows through conversational interfaces.
This guide covers authentication strategies, GraphQL operations, board and item management, webhook automation, and advanced features like file uploads and search. Each section includes production-ready TypeScript code with error handling, rate limiting, and best practices for enterprise-grade integrations.
Whether you're building a project management chatbot, creating workflow automation tools, or developing custom team collaboration apps, this guide provides the technical foundation for seamless Monday.com integration.
By the end of this article, you'll have a complete toolkit for building Monday.com integrations that enable users to create boards, manage items, process webhooks, upload files, and automate complex workflows—all through natural language conversations with ChatGPT.
Monday.com API Authentication
Monday.com supports two authentication methods: API tokens for personal apps and OAuth 2.0 for public integrations. API tokens provide quick access for development and internal tools, while OAuth enables secure third-party app distribution with granular permission scopes.
API Token Authentication is the simplest approach. Users generate personal tokens from their Monday.com account settings (Profile → Admin → API). Each token inherits the user's permissions and never expires unless manually revoked. Store tokens securely in environment variables or encrypted secrets management systems—never commit them to version control.
OAuth 2.0 Flow is required for apps distributed to multiple Monday.com accounts. The authorization flow follows standard OAuth patterns: redirect users to Monday.com's authorization endpoint, receive an authorization code, exchange it for access tokens, and refresh tokens before expiry. Monday.com provides detailed OAuth documentation at https://developer.monday.com/apps/docs/oauth.
Rate Limits vary by account tier. Free accounts allow 60 requests per minute, while enterprise plans support up to 1,000 requests per minute. The API returns rate limit information in response headers (X-RateLimit-Remaining, X-RateLimit-Reset). Implement exponential backoff when rate limits are exceeded to maintain reliability.
Scopes control access permissions. Common scopes include boards:read, boards:write, workspaces:read, and me:read. Request only necessary scopes to minimize security risks and improve user trust. The complete scope reference is available in the Monday.com API documentation.
Here's a production-ready Monday.com GraphQL client with authentication, rate limiting, and error handling:
// monday-client.ts - Monday.com GraphQL Client (TypeScript)
import axios, { AxiosInstance, AxiosError } from 'axios';
interface MondayConfig {
apiToken: string;
apiVersion?: string;
maxRetries?: number;
retryDelay?: number;
}
interface RateLimit {
remaining: number;
reset: number;
limit: number;
}
interface MondayError {
message: string;
code: string;
statusCode: number;
retryAfter?: number;
}
export class MondayClient {
private client: AxiosInstance;
private config: Required<MondayConfig>;
private rateLimit: RateLimit;
constructor(config: MondayConfig) {
this.config = {
apiVersion: '2023-10',
maxRetries: 3,
retryDelay: 1000,
...config,
};
this.client = axios.create({
baseURL: 'https://api.monday.com/v2',
headers: {
'Authorization': this.config.apiToken,
'Content-Type': 'application/json',
'API-Version': this.config.apiVersion,
},
timeout: 30000,
});
this.rateLimit = {
remaining: 60,
reset: Date.now() + 60000,
limit: 60,
};
this.setupInterceptors();
}
private setupInterceptors(): void {
// Response interceptor for rate limit tracking
this.client.interceptors.response.use(
(response) => {
this.updateRateLimit(response.headers);
return response;
},
async (error: AxiosError) => {
if (error.response?.status === 429) {
return this.handleRateLimit(error);
}
throw this.transformError(error);
}
);
}
private updateRateLimit(headers: any): void {
const remaining = headers['x-ratelimit-remaining'];
const reset = headers['x-ratelimit-reset'];
const limit = headers['x-ratelimit-limit'];
if (remaining !== undefined) {
this.rateLimit = {
remaining: parseInt(remaining, 10),
reset: parseInt(reset, 10) * 1000,
limit: parseInt(limit, 10),
};
}
}
private async handleRateLimit(error: AxiosError): Promise<any> {
const retryAfter = error.response?.headers['retry-after'];
const delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : this.config.retryDelay;
console.warn(`Rate limit exceeded. Retrying after ${delay}ms`);
await this.sleep(delay);
// Retry original request
return this.client.request(error.config!);
}
private transformError(error: AxiosError): MondayError {
const statusCode = error.response?.status || 500;
const data: any = error.response?.data;
return {
message: data?.error_message || error.message || 'Unknown error',
code: data?.error_code || 'UNKNOWN_ERROR',
statusCode,
retryAfter: error.response?.headers['retry-after'],
};
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
async query<T = any>(
query: string,
variables?: Record<string, any>
): Promise<T> {
let lastError: MondayError | null = null;
for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) {
try {
// Check rate limit before request
if (this.rateLimit.remaining <= 1) {
const waitTime = Math.max(0, this.rateLimit.reset - Date.now());
if (waitTime > 0) {
console.warn(`Rate limit low. Waiting ${waitTime}ms`);
await this.sleep(waitTime);
}
}
const response = await this.client.post('', {
query,
variables,
});
// Check for GraphQL errors
if (response.data.errors && response.data.errors.length > 0) {
throw {
message: response.data.errors[0].message,
code: 'GRAPHQL_ERROR',
statusCode: 400,
};
}
return response.data.data as T;
} catch (error) {
lastError = error as MondayError;
// Don't retry on client errors (4xx except 429)
if (lastError.statusCode >= 400 && lastError.statusCode < 500 && lastError.statusCode !== 429) {
throw lastError;
}
// Exponential backoff for retries
if (attempt < this.config.maxRetries) {
const delay = this.config.retryDelay * Math.pow(2, attempt - 1);
console.warn(`Attempt ${attempt} failed. Retrying after ${delay}ms`);
await this.sleep(delay);
}
}
}
throw lastError || new Error('Max retries exceeded');
}
getRateLimit(): RateLimit {
return { ...this.rateLimit };
}
}
This client provides automatic rate limiting, exponential backoff, and comprehensive error handling. For enterprise integrations, extend this base client with custom logging, metrics collection, and circuit breaker patterns.
GraphQL Operations
Monday.com's GraphQL API follows the GraphQL specification with queries for data retrieval, mutations for modifications, and complexity scoring for rate limiting. Understanding GraphQL fundamentals is essential for building efficient integrations.
Queries fetch data without side effects. Common queries include boards, items, users, and workspaces. Use GraphQL fragments to reuse field selections across queries and mutations. Query complexity is calculated based on requested fields and nested relationships—keep complexity under 10 million for optimal performance.
Mutations modify server state. All write operations (create, update, delete) use mutations. Monday.com mutations follow a consistent pattern: operation name, input parameters, and return fields. Always request the id field in mutation responses for subsequent operations.
Subscriptions enable real-time updates via webhooks (covered in the webhook section). While Monday.com doesn't support GraphQL subscription protocol, webhooks provide equivalent functionality for event-driven architectures.
Complexity Management prevents excessive resource usage. Each field has an assigned complexity score. The API rejects queries exceeding complexity limits. Use pagination (limit parameter) and selective field requests to control complexity. The API returns current complexity in response headers.
Here's a comprehensive board manager demonstrating GraphQL queries and mutations:
// board-manager.ts - Monday.com Board Manager (TypeScript)
import { MondayClient } from './monday-client';
export interface Board {
id: string;
name: string;
description?: string;
state: 'active' | 'archived' | 'deleted';
board_kind: 'public' | 'private' | 'share';
workspace_id?: string;
owners: User[];
groups: Group[];
columns: Column[];
}
export interface Group {
id: string;
title: string;
color: string;
position: number;
}
export interface Column {
id: string;
title: string;
type: string;
settings_str?: string;
}
export interface User {
id: string;
name: string;
email: string;
}
export class BoardManager {
constructor(private client: MondayClient) {}
async listBoards(
workspaceId?: string,
state: 'active' | 'archived' | 'all' = 'active'
): Promise<Board[]> {
const query = `
query ListBoards($workspaceIds: [ID!], $state: State) {
boards(workspace_ids: $workspaceIds, state: $state, limit: 100) {
id
name
description
state
board_kind
workspace_id
owners {
id
name
email
}
groups {
id
title
color
position
}
columns {
id
title
type
settings_str
}
}
}
`;
const variables: any = { state };
if (workspaceId) {
variables.workspaceIds = [workspaceId];
}
const result = await this.client.query<{ boards: Board[] }>(query, variables);
return result.boards;
}
async getBoard(boardId: string): Promise<Board> {
const query = `
query GetBoard($boardId: ID!) {
boards(ids: [$boardId]) {
id
name
description
state
board_kind
workspace_id
owners {
id
name
email
}
groups {
id
title
color
position
}
columns {
id
title
type
settings_str
}
}
}
`;
const result = await this.client.query<{ boards: Board[] }>(query, {
boardId,
});
if (!result.boards || result.boards.length === 0) {
throw new Error(`Board not found: ${boardId}`);
}
return result.boards[0];
}
async createBoard(
name: string,
options: {
description?: string;
boardKind?: 'public' | 'private' | 'share';
workspaceId?: string;
templateId?: string;
} = {}
): Promise<Board> {
const mutation = `
mutation CreateBoard(
$name: String!
$boardKind: BoardKind
$description: String
$workspaceId: ID
$templateId: ID
) {
create_board(
board_name: $name
board_kind: $boardKind
description: $description
workspace_id: $workspaceId
template_id: $templateId
) {
id
name
description
state
board_kind
workspace_id
}
}
`;
const result = await this.client.query<{ create_board: Board }>(mutation, {
name,
boardKind: options.boardKind,
description: options.description,
workspaceId: options.workspaceId,
templateId: options.templateId,
});
return result.create_board;
}
async updateBoard(
boardId: string,
updates: {
name?: string;
description?: string;
}
): Promise<Board> {
const mutation = `
mutation UpdateBoard(
$boardId: ID!
$name: String
$description: String
) {
update_board(
board_id: $boardId
board_attribute: { name: $name, description: $description }
) {
id
name
description
}
}
`;
const result = await this.client.query<{ update_board: Board }>(mutation, {
boardId,
...updates,
});
return result.update_board;
}
async archiveBoard(boardId: string): Promise<Board> {
const mutation = `
mutation ArchiveBoard($boardId: ID!) {
archive_board(board_id: $boardId) {
id
state
}
}
`;
const result = await this.client.query<{ archive_board: Board }>(mutation, {
boardId,
});
return result.archive_board;
}
async deleteBoard(boardId: string): Promise<void> {
const mutation = `
mutation DeleteBoard($boardId: ID!) {
delete_board(board_id: $boardId) {
id
}
}
`;
await this.client.query(mutation, { boardId });
}
}
This board manager provides complete CRUD operations with type safety and error handling. For SaaS integrations, extend this manager with caching, batch operations, and permission checks.
Board & Item Management
Items represent tasks, leads, projects, or any entity tracked in Monday.com boards. Each item contains column values (status, text, numbers, dates, etc.) organized within groups. Understanding the item data model is crucial for building effective task management integrations.
Item Creation requires a board ID, item name, and optional group ID. Column values can be set during creation or updated separately. Each column type has a specific JSON format for values—status columns use label IDs, date columns use ISO strings, and people columns use user IDs.
Column Values are stored as JSON strings in Monday.com's API. Parse and stringify values carefully to avoid data corruption. The API provides column metadata (type, settings) to validate values before updates. Use column IDs (not titles) for reliable references across board updates.
Groups organize items within boards. Each board has at least one group. Items can be moved between groups, and groups can be reordered, colored, and collapsed. When creating items without specifying a group, Monday.com assigns them to the first group by default.
Subitems create hierarchical relationships. A subitem belongs to exactly one parent item and inherits the parent board's column structure. Subitems are useful for breaking down complex tasks, tracking dependencies, and managing multi-step workflows.
Here's a complete item manager with CRUD operations and column value handling:
// item-manager.ts - Monday.com Item Manager (TypeScript)
import { MondayClient } from './monday-client';
export interface Item {
id: string;
name: string;
state: 'active' | 'archived' | 'deleted';
board: {
id: string;
};
group: {
id: string;
title: string;
};
column_values: ColumnValue[];
subitems?: Item[];
parent_item?: {
id: string;
};
}
export interface ColumnValue {
id: string;
title: string;
type: string;
text?: string;
value?: string;
additional_info?: any;
}
export class ItemManager {
constructor(private client: MondayClient) {}
async listItems(
boardId: string,
options: {
groupId?: string;
columnIds?: string[];
limit?: number;
page?: number;
} = {}
): Promise<Item[]> {
const query = `
query ListItems(
$boardId: ID!
$groupId: String
$columnIds: [String!]
$limit: Int
$page: Int
) {
boards(ids: [$boardId]) {
items_page(
query_params: {
rules: [{ column_id: "group", compare_value: [$groupId] }]
}
limit: $limit
page: $page
) {
items {
id
name
state
group {
id
title
}
column_values(ids: $columnIds) {
id
title
type
text
value
additional_info
}
}
}
}
}
`;
const result = await this.client.query<{
boards: Array<{ items_page: { items: Item[] } }>;
}>(query, {
boardId,
groupId: options.groupId,
columnIds: options.columnIds,
limit: options.limit || 100,
page: options.page || 1,
});
return result.boards[0]?.items_page?.items || [];
}
async getItem(itemId: string, columnIds?: string[]): Promise<Item> {
const query = `
query GetItem($itemId: ID!, $columnIds: [String!]) {
items(ids: [$itemId]) {
id
name
state
board {
id
}
group {
id
title
}
column_values(ids: $columnIds) {
id
title
type
text
value
additional_info
}
subitems {
id
name
column_values {
id
text
value
}
}
}
}
`;
const result = await this.client.query<{ items: Item[] }>(query, {
itemId,
columnIds,
});
if (!result.items || result.items.length === 0) {
throw new Error(`Item not found: ${itemId}`);
}
return result.items[0];
}
async createItem(
boardId: string,
itemName: string,
options: {
groupId?: string;
columnValues?: Record<string, any>;
createLabelsIfMissing?: boolean;
} = {}
): Promise<Item> {
const mutation = `
mutation CreateItem(
$boardId: ID!
$itemName: String!
$groupId: String
$columnValues: JSON
$createLabels: Boolean
) {
create_item(
board_id: $boardId
item_name: $itemName
group_id: $groupId
column_values: $columnValues
create_labels_if_missing: $createLabels
) {
id
name
board {
id
}
group {
id
title
}
column_values {
id
title
text
value
}
}
}
`;
const result = await this.client.query<{ create_item: Item }>(mutation, {
boardId,
itemName,
groupId: options.groupId,
columnValues: options.columnValues
? JSON.stringify(options.columnValues)
: undefined,
createLabels: options.createLabelsIfMissing || false,
});
return result.create_item;
}
async updateItem(
itemId: string,
updates: {
name?: string;
columnValues?: Record<string, any>;
}
): Promise<Item> {
const mutations: string[] = [];
const variables: any = { itemId };
if (updates.name) {
mutations.push(`
name: change_simple_column_value(
item_id: $itemId
column_id: "name"
value: $name
) {
id
name
}
`);
variables.name = updates.name;
}
if (updates.columnValues) {
mutations.push(`
columns: change_multiple_column_values(
item_id: $itemId
column_values: $columnValues
) {
id
column_values {
id
text
value
}
}
`);
variables.columnValues = JSON.stringify(updates.columnValues);
}
const mutation = `
mutation UpdateItem($itemId: ID!, $name: String, $columnValues: JSON) {
${mutations.join('\n')}
}
`;
const result = await this.client.query(mutation, variables);
return result.columns || result.name;
}
async deleteItem(itemId: string): Promise<void> {
const mutation = `
mutation DeleteItem($itemId: ID!) {
delete_item(item_id: $itemId) {
id
}
}
`;
await this.client.query(mutation, { itemId });
}
async createSubitem(
parentItemId: string,
subitemName: string,
columnValues?: Record<string, any>
): Promise<Item> {
const mutation = `
mutation CreateSubitem(
$parentItemId: ID!
$subitemName: String!
$columnValues: JSON
) {
create_subitem(
parent_item_id: $parentItemId
item_name: $subitemName
column_values: $columnValues
) {
id
name
board {
id
}
parent_item {
id
}
column_values {
id
text
value
}
}
}
`;
const result = await this.client.query<{ create_subitem: Item }>(mutation, {
parentItemId,
subitemName,
columnValues: columnValues ? JSON.stringify(columnValues) : undefined,
});
return result.create_subitem;
}
}
This item manager handles all common operations with proper JSON serialization for column values. For CRM integrations, extend this manager with custom field mapping and validation logic.
Column Value Handler
Column values require special handling due to Monday.com's JSON string format. Each column type (status, date, numbers, people, etc.) has a unique value structure. This handler provides type-safe column value management for all common column types.
Status Columns store label indices or label IDs. Query the column settings to map label text to IDs. Status columns support custom colors and labels per board. Always validate label existence before setting values to avoid errors.
Date Columns accept ISO 8601 date strings. Include optional time values for datetime columns. Monday.com displays dates in user-local timezones, but stores UTC internally. Handle timezone conversions carefully for international teams.
Numbers Columns store numeric values as strings. Monday.com supports integers and decimals with configurable formatting (currency, percentage, etc.). Validate numeric ranges and precision requirements before updates.
People Columns reference user IDs. Query workspace users to validate IDs before assignment. People columns support multiple assignees depending on column settings. Always check the settings_str field for column-specific constraints.
Here's a comprehensive column value handler supporting all major column types:
// column-handler.ts - Monday.com Column Value Handler (TypeScript)
import { MondayClient } from './monday-client';
export interface StatusValue {
label: string;
label_id?: number;
}
export interface DateValue {
date: string;
time?: string;
timezone?: string;
}
export interface PeopleValue {
personsAndTeams: Array<{
id: number;
kind: 'person' | 'team';
}>;
}
export interface NumberValue {
value: number;
unit?: string;
}
export class ColumnValueHandler {
constructor(private client: MondayClient) {}
formatStatusValue(label: string, labelId?: number): string {
const value: StatusValue = { label };
if (labelId !== undefined) {
value.label_id = labelId;
}
return JSON.stringify(value);
}
formatDateValue(
date: string | Date,
options: { includeTime?: boolean; timezone?: string } = {}
): string {
const dateObj = typeof date === 'string' ? new Date(date) : date;
const dateStr = dateObj.toISOString().split('T')[0];
const value: DateValue = { date: dateStr };
if (options.includeTime) {
value.time = dateObj.toISOString().split('T')[1].slice(0, 8);
}
if (options.timezone) {
value.timezone = options.timezone;
}
return JSON.stringify(value);
}
formatPeopleValue(personIds: number[]): string {
const value: PeopleValue = {
personsAndTeams: personIds.map(id => ({
id,
kind: 'person' as const,
})),
};
return JSON.stringify(value);
}
formatNumberValue(num: number, unit?: string): string {
const value: NumberValue = { value: num };
if (unit) {
value.unit = unit;
}
return JSON.stringify(value);
}
formatTextValue(text: string): string {
return JSON.stringify(text);
}
formatLinkValue(url: string, text?: string): string {
return JSON.stringify({
url,
text: text || url,
});
}
formatEmailValue(email: string, text?: string): string {
return JSON.stringify({
email,
text: text || email,
});
}
formatPhoneValue(phone: string, countryCode?: string): string {
return JSON.stringify({
phone,
countryShortName: countryCode || 'US',
});
}
formatTimelineValue(from: string | Date, to: string | Date): string {
const fromDate = typeof from === 'string' ? new Date(from) : from;
const toDate = typeof to === 'string' ? new Date(to) : to;
return JSON.stringify({
from: fromDate.toISOString().split('T')[0],
to: toDate.toISOString().split('T')[0],
});
}
formatDropdownValue(ids: number[]): string {
return JSON.stringify({ ids });
}
parseStatusValue(value: string): StatusValue | null {
try {
return JSON.parse(value || '{}');
} catch {
return null;
}
}
parseDateValue(value: string): DateValue | null {
try {
return JSON.parse(value || '{}');
} catch {
return null;
}
}
parsePeopleValue(value: string): number[] {
try {
const parsed: PeopleValue = JSON.parse(value || '{}');
return parsed.personsAndTeams?.map(p => p.id) || [];
} catch {
return [];
}
}
parseNumberValue(value: string): number | null {
try {
const parsed: NumberValue = JSON.parse(value || '{}');
return parsed.value ?? null;
} catch {
return null;
}
}
async getColumnSettings(boardId: string, columnId: string): Promise<any> {
const query = `
query GetColumnSettings($boardId: ID!) {
boards(ids: [$boardId]) {
columns {
id
title
type
settings_str
}
}
}
`;
const result = await this.client.query<{
boards: Array<{ columns: Array<{ id: string; settings_str: string }> }>;
}>(query, { boardId });
const column = result.boards[0]?.columns?.find(c => c.id === columnId);
if (!column) {
throw new Error(`Column not found: ${columnId}`);
}
return JSON.parse(column.settings_str || '{}');
}
async getStatusLabels(boardId: string, columnId: string): Promise<Map<string, number>> {
const settings = await this.getColumnSettings(boardId, columnId);
const labels = new Map<string, number>();
if (settings.labels) {
Object.entries(settings.labels).forEach(([id, label]: [string, any]) => {
labels.set(label.name || label, parseInt(id, 10));
});
}
return labels;
}
}
This handler provides type-safe column value formatting and parsing. For data integration workflows, combine this handler with validation schemas and transformation pipelines.
Webhook Automation
Webhooks enable real-time event processing for Monday.com changes. When boards, items, or column values change, Monday.com sends HTTP POST requests to your webhook endpoint. This enables event-driven architectures, real-time notifications, and automated workflows.
Webhook Events include create_item, change_column_value, create_update, change_status_column, and more. Each event includes the triggering entity ID, board ID, user ID, and changed values. Filter events by board, column, or specific value changes to reduce noise.
Webhook Configuration requires a publicly accessible HTTPS endpoint. Monday.com validates SSL certificates and rejects self-signed certificates. Use services like ngrok for development or deploy to production hosting with valid certificates. Configure webhooks via the API or Monday.com's web interface.
Payload Processing requires signature verification to prevent spoofed requests. Monday.com includes an Authorization header with each webhook request. Verify signatures using your webhook secret before processing payloads. Implement idempotency keys to handle duplicate webhook deliveries.
Real-time Updates combine webhooks with WebSocket connections to push changes to ChatGPT apps instantly. Store webhook payloads in message queues (Redis, RabbitMQ) for reliable processing. Implement retry logic for failed webhook handlers with exponential backoff.
Here's a production-ready webhook event processor with signature verification and event routing:
// webhook-processor.ts - Monday.com Webhook Event Processor (TypeScript)
import crypto from 'crypto';
import { MondayClient } from './monday-client';
export interface WebhookEvent {
event: {
type: string;
userId: string;
boardId: string;
itemId?: string;
columnId?: string;
value?: any;
previousValue?: any;
pulseId?: string;
groupId?: string;
};
subscriptionId: string;
}
export interface WebhookConfig {
boardId: string;
url: string;
event: string;
config?: {
columnId?: string;
};
}
type EventHandler = (event: WebhookEvent) => Promise<void>;
export class WebhookProcessor {
private handlers: Map<string, EventHandler[]> = new Map();
private webhookSecret: string;
constructor(
private client: MondayClient,
webhookSecret: string
) {
this.webhookSecret = webhookSecret;
}
async createWebhook(config: WebhookConfig): Promise<string> {
const mutation = `
mutation CreateWebhook(
$boardId: ID!
$url: String!
$event: WebhookEventType!
$config: JSON
) {
create_webhook(
board_id: $boardId
url: $url
event: $event
config: $config
) {
id
board_id
webhook_url
event
}
}
`;
const result = await this.client.query<{
create_webhook: { id: string };
}>(mutation, {
boardId: config.boardId,
url: config.url,
event: config.event,
config: config.config ? JSON.stringify(config.config) : undefined,
});
return result.create_webhook.id;
}
async deleteWebhook(webhookId: string): Promise<void> {
const mutation = `
mutation DeleteWebhook($webhookId: ID!) {
delete_webhook(id: $webhookId) {
id
}
}
`;
await this.client.query(mutation, { webhookId });
}
verifySignature(payload: string, signature: string): boolean {
const hmac = crypto.createHmac('sha256', this.webhookSecret);
hmac.update(payload);
const expected = hmac.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
on(eventType: string, handler: EventHandler): void {
if (!this.handlers.has(eventType)) {
this.handlers.set(eventType, []);
}
this.handlers.get(eventType)!.push(handler);
}
off(eventType: string, handler?: EventHandler): void {
if (!handler) {
this.handlers.delete(eventType);
return;
}
const handlers = this.handlers.get(eventType);
if (handlers) {
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
}
}
async processWebhook(
payload: string,
signature: string
): Promise<{ success: boolean; error?: string }> {
// Verify signature
if (!this.verifySignature(payload, signature)) {
return {
success: false,
error: 'Invalid signature',
};
}
let event: WebhookEvent;
try {
event = JSON.parse(payload);
} catch (error) {
return {
success: false,
error: 'Invalid JSON payload',
};
}
// Route event to handlers
const eventType = event.event.type;
const handlers = this.handlers.get(eventType) || [];
if (handlers.length === 0) {
console.warn(`No handlers registered for event type: ${eventType}`);
return { success: true };
}
// Process handlers in parallel
const results = await Promise.allSettled(
handlers.map(handler => handler(event))
);
// Check for failures
const failures = results.filter(r => r.status === 'rejected');
if (failures.length > 0) {
console.error(
`${failures.length} handler(s) failed for event ${eventType}`,
failures
);
return {
success: false,
error: `${failures.length} handler(s) failed`,
};
}
return { success: true };
}
// Convenience methods for common events
onItemCreated(handler: EventHandler): void {
this.on('create_item', handler);
}
onItemUpdated(handler: EventHandler): void {
this.on('change_column_value', handler);
}
onItemDeleted(handler: EventHandler): void {
this.on('delete_item', handler);
}
onStatusChanged(handler: EventHandler): void {
this.on('change_status_column', handler);
}
onUpdateCreated(handler: EventHandler): void {
this.on('create_update', handler);
}
}
// Example usage
/*
const processor = new WebhookProcessor(client, process.env.WEBHOOK_SECRET!);
processor.onItemCreated(async (event) => {
console.log('New item created:', event.event.itemId);
// Send ChatGPT notification, create task, etc.
});
processor.onStatusChanged(async (event) => {
console.log('Status changed:', event.event.value);
// Trigger automation workflow
});
// Express endpoint
app.post('/webhooks/monday', async (req, res) => {
const signature = req.headers['authorization'];
const payload = JSON.stringify(req.body);
const result = await processor.processWebhook(payload, signature);
res.status(result.success ? 200 : 400).json(result);
});
*/
This webhook processor handles signature verification, event routing, and error handling. For notification systems, combine webhooks with push notification services and email automation.
Advanced Features
Monday.com provides advanced features for file management, updates (comments), notifications, and search. These features enable rich collaboration experiences within ChatGPT apps.
File Uploads support attachments on items and updates. Upload files to Monday.com's storage or reference external URLs. The API supports multipart form uploads with file metadata (name, type, size). Large files should be streamed to avoid memory issues.
Updates are conversation threads attached to items. Create updates with text, mentions, and file attachments. Updates support rich formatting and emoji reactions. Query update history with pagination for performance.
Search enables full-text search across boards, items, and updates. The API supports fuzzy matching, field-specific searches, and result ranking. Implement debouncing for search-as-you-type interfaces to reduce API calls.
Here's an implementation of file upload, update management, and search functionality:
// advanced-features.ts - Monday.com Advanced Features (TypeScript)
import { MondayClient } from './monday-client';
import FormData from 'form-data';
import fs from 'fs';
export interface Update {
id: string;
body: string;
created_at: string;
creator: {
id: string;
name: string;
};
text_body: string;
replies?: Update[];
}
export interface FileAsset {
id: string;
name: string;
url: string;
file_extension: string;
file_size: number;
created_at: string;
}
export class AdvancedFeatures {
constructor(private client: MondayClient) {}
async uploadFile(
itemId: string,
filePath: string
): Promise<FileAsset> {
const formData = new FormData();
formData.append('query', `
mutation AddFile($itemId: ID!, $file: File!) {
add_file_to_column(
item_id: $itemId
column_id: "files"
file: $file
) {
id
name
url
file_extension
file_size
created_at
}
}
`);
formData.append('variables', JSON.stringify({ itemId }));
formData.append('file', fs.createReadStream(filePath));
// Note: This requires custom axios config for multipart
const result = await this.client.query<{
add_file_to_column: FileAsset;
}>('', {});
return result.add_file_to_column;
}
async createUpdate(
itemId: string,
body: string,
options: {
parentId?: string;
} = {}
): Promise<Update> {
const mutation = `
mutation CreateUpdate(
$itemId: ID!
$body: String!
$parentId: ID
) {
create_update(
item_id: $itemId
body: $body
parent_id: $parentId
) {
id
body
text_body
created_at
creator {
id
name
}
}
}
`;
const result = await this.client.query<{
create_update: Update;
}>(mutation, {
itemId,
body,
parentId: options.parentId,
});
return result.create_update;
}
async getUpdates(
itemId: string,
limit: number = 25
): Promise<Update[]> {
const query = `
query GetUpdates($itemId: ID!, $limit: Int) {
items(ids: [$itemId]) {
updates(limit: $limit) {
id
body
text_body
created_at
creator {
id
name
}
replies {
id
body
text_body
created_at
creator {
id
name
}
}
}
}
}
`;
const result = await this.client.query<{
items: Array<{ updates: Update[] }>;
}>(query, {
itemId,
limit,
});
return result.items[0]?.updates || [];
}
async deleteUpdate(updateId: string): Promise<void> {
const mutation = `
mutation DeleteUpdate($updateId: ID!) {
delete_update(id: $updateId) {
id
}
}
`;
await this.client.query(mutation, { updateId });
}
async searchItems(
query: string,
options: {
boardIds?: string[];
columnIds?: string[];
limit?: number;
} = {}
): Promise<any[]> {
const searchQuery = `
query SearchItems(
$query: String!
$boardIds: [ID!]
$columnIds: [String!]
$limit: Int
) {
search_items(
query: $query
board_ids: $boardIds
column_ids: $columnIds
limit: $limit
) {
id
name
board {
id
name
}
column_values {
id
text
}
}
}
`;
const result = await this.client.query<{
search_items: any[];
}>(searchQuery, {
query,
boardIds: options.boardIds,
columnIds: options.columnIds,
limit: options.limit || 25,
});
return result.search_items;
}
async addTag(itemId: string, tagId: number): Promise<void> {
const mutation = `
mutation AddTag($itemId: ID!, $tagId: Int!) {
add_tag_to_item(item_id: $itemId, tag_id: $tagId) {
id
}
}
`;
await this.client.query(mutation, { itemId, tagId });
}
async removeTag(itemId: string, tagId: number): Promise<void> {
const mutation = `
mutation RemoveTag($itemId: ID!, $tagId: Int!) {
remove_tag_from_item(item_id: $itemId, tag_id: $tagId) {
id
}
}
`;
await this.client.query(mutation, { itemId, tagId });
}
}
These advanced features complete the Monday.com integration toolkit. For document management systems, extend file upload functionality with document parsing and version control.
Conclusion
Building Monday.com integrations for ChatGPT apps enables powerful project management automation through natural language interfaces. This guide provided production-ready TypeScript code for authentication, GraphQL operations, board and item management, column value handling, webhook processing, and advanced features.
The Monday.com GraphQL API offers comprehensive access to all platform features with efficient data fetching, real-time webhooks, and flexible querying. Combined with ChatGPT's conversational AI, users can create boards, manage tasks, process updates, and automate workflows without leaving their chat interface.
Start building your Monday.com integration today with MakeAIHQ.com—the no-code platform for creating ChatGPT apps. Our AI Conversational Editor and Instant App Wizard provide visual tools for building integrations without writing code. For technical teams, our platform exports production-ready MCP servers and deployment packages.
Ready to transform project management with AI? Sign up for free and deploy your Monday.com ChatGPT integration in 48 hours. Join thousands of developers building the future of conversational project management.
Related Articles:
- Project Management Chatbot: Complete Implementation Guide
- Asana Integration for ChatGPT Apps
- Trello Integration for ChatGPT Apps
- Task Management ChatGPT Integration
- Workflow Automation with ChatGPT
- Team Collaboration ChatGPT Apps
- CRM ChatGPT Integration Guide
- Data Integration with ChatGPT
- Notification Systems for ChatGPT Apps
- Document Management ChatGPT Integration
External Resources: