Skip to main content
Glama
zqushair
by zqushair
webhookValidation.ts21.9 kB
import { Request, Response, NextFunction } from 'express'; import { validationUtil } from '../utils/validation.js'; import logger from '../utils/logger.js'; /** * Webhook Validation Middleware * This middleware validates webhook payloads against schemas */ export class WebhookValidationMiddleware { /** * Create a middleware function that validates webhook payloads against a schema * @param eventTypeToSchemaMap A map of event types to schemas * @returns A middleware function that validates webhook payloads */ public static validateWebhook(eventTypeToSchemaMap: Record<string, any>) { return (req: Request, res: Response, next: NextFunction) => { try { // Get the event type from the request body const eventType = req.body.type; // If the event type is not in the map, skip validation if (!eventTypeToSchemaMap[eventType]) { logger.warn(`No schema found for event type: ${eventType}`); return next(); } // Get the schema for the event type const schema = eventTypeToSchemaMap[eventType]; // Validate the webhook payload const result = validationUtil.validateWithResult(req.body, (value) => { // Use the appropriate validation method based on the schema type if (schema.type === 'object') { return validationUtil.validateObject(value, schema.options); } else if (schema.validator) { return validationUtil.validateCustom(value, schema.validator); } else { throw new Error(`Unsupported schema type: ${schema.type}`); } }); // If the webhook payload is invalid, return an error response if (!result.valid) { logger.warn('Webhook validation failed', { errors: result.errors, body: req.body, path: req.path, method: req.method, eventType, }); return res.status(400).json({ error: 'Webhook validation failed', details: result.errors, }); } // Replace the request body with the validated data req.body = result.data; // Continue to the next middleware next(); } catch (error: any) { logger.error('Webhook validation middleware error', { error: error.message, stack: error.stack, path: req.path, method: req.method, body: req.body, }); // Return an error response return res.status(500).json({ error: 'Internal server error', }); } }; } /** * Create a middleware function that validates conversation webhook payloads * @returns A middleware function that validates conversation webhook payloads */ public static validateConversationWebhook() { // Define schemas for conversation webhook events const eventTypeToSchemaMap: Record<string, any> = { 'conversation.created': { type: 'object', options: { properties: { type: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^conversation\.created$/, }), }, payload: { required: true, validator: (value: unknown) => validationUtil.validateObject(value, { required: true, properties: { id: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^cnv_[a-zA-Z0-9]+$/, }), }, subject: { required: false, validator: (value: unknown) => validationUtil.validateString(value, { required: false, }), }, status: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^(assigned|unassigned|archived)$/, }), }, assignee: { required: false, validator: (value: unknown) => { if (value === null) return null; return validationUtil.validateObject(value, { required: false, properties: { id: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^tea_[a-zA-Z0-9]+$/, }), }, email: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, }), }, username: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, }), }, }, }); }, }, recipient: { required: true, validator: (value: unknown) => validationUtil.validateObject(value, { required: true, properties: { handle: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, }), }, role: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^(from|to|cc|bcc)$/, }), }, }, }), }, tags: { required: false, validator: (value: unknown) => validationUtil.validateArray(value, { required: false, itemValidator: (item: unknown) => validationUtil.validateObject(item, { required: true, properties: { id: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^tag_[a-zA-Z0-9]+$/, }), }, name: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, }), }, }, }), }), }, }, }), }, }, }, }, 'conversation.assigned': { type: 'object', options: { properties: { type: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^conversation\.assigned$/, }), }, payload: { required: true, validator: (value: unknown) => validationUtil.validateObject(value, { required: true, properties: { id: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^cnv_[a-zA-Z0-9]+$/, }), }, assignee: { required: true, validator: (value: unknown) => validationUtil.validateObject(value, { required: true, properties: { id: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^tea_[a-zA-Z0-9]+$/, }), }, email: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, }), }, username: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, }), }, }, }), }, }, }), }, }, }, }, // Add more schemas for other conversation webhook events }; return WebhookValidationMiddleware.validateWebhook(eventTypeToSchemaMap); } /** * Create a middleware function that validates message webhook payloads * @returns A middleware function that validates message webhook payloads */ public static validateMessageWebhook() { // Define schemas for message webhook events const eventTypeToSchemaMap: Record<string, any> = { 'message.created': { type: 'object', options: { properties: { type: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^message\.created$/, }), }, payload: { required: true, validator: (value: unknown) => validationUtil.validateObject(value, { required: true, properties: { id: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^msg_[a-zA-Z0-9]+$/, }), }, conversation_id: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^cnv_[a-zA-Z0-9]+$/, }), }, type: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^(email|comment|draft)$/, }), }, is_inbound: { required: true, validator: (value: unknown) => validationUtil.validateBoolean(value, { required: true, }), }, author: { required: false, validator: (value: unknown) => { if (value === null) return null; return validationUtil.validateObject(value, { required: false, properties: { id: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^(tea|con)_[a-zA-Z0-9]+$/, }), }, email: { required: false, validator: (value: unknown) => validationUtil.validateString(value, { required: false, pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, }), }, username: { required: false, validator: (value: unknown) => validationUtil.validateString(value, { required: false, }), }, }, }); }, }, body: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, }), }, text: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, }), }, attachments: { required: false, validator: (value: unknown) => validationUtil.validateArray(value, { required: false, itemValidator: (item: unknown) => validationUtil.validateObject(item, { required: true, properties: { id: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^att_[a-zA-Z0-9]+$/, }), }, filename: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, }), }, url: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^https?:\/\/.+$/, }), }, content_type: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, }), }, size: { required: true, validator: (value: unknown) => validationUtil.validateNumber(value, { required: true, min: 0, }), }, }, }), }), }, }, }), }, }, }, }, // Add more schemas for other message webhook events }; return WebhookValidationMiddleware.validateWebhook(eventTypeToSchemaMap); } /** * Create a middleware function that validates contact webhook payloads * @returns A middleware function that validates contact webhook payloads */ public static validateContactWebhook() { // Define schemas for contact webhook events const eventTypeToSchemaMap: Record<string, any> = { 'contact.created': { type: 'object', options: { properties: { type: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^contact\.created$/, }), }, payload: { required: true, validator: (value: unknown) => validationUtil.validateObject(value, { required: true, properties: { id: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^con_[a-zA-Z0-9]+$/, }), }, name: { required: false, validator: (value: unknown) => validationUtil.validateString(value, { required: false, }), }, description: { required: false, validator: (value: unknown) => validationUtil.validateString(value, { required: false, }), }, handles: { required: true, validator: (value: unknown) => validationUtil.validateArray(value, { required: true, itemValidator: (item: unknown) => validationUtil.validateObject(item, { required: true, properties: { handle: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, }), }, source: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^(email|phone|twitter|facebook|intercom|custom)$/, }), }, }, }), }), }, }, }), }, }, }, }, 'contact.updated': { type: 'object', options: { properties: { type: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^contact\.updated$/, }), }, payload: { required: true, validator: (value: unknown) => validationUtil.validateObject(value, { required: true, properties: { id: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^con_[a-zA-Z0-9]+$/, }), }, name: { required: false, validator: (value: unknown) => validationUtil.validateString(value, { required: false, }), }, description: { required: false, validator: (value: unknown) => validationUtil.validateString(value, { required: false, }), }, handles: { required: true, validator: (value: unknown) => validationUtil.validateArray(value, { required: true, itemValidator: (item: unknown) => validationUtil.validateObject(item, { required: true, properties: { handle: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, }), }, source: { required: true, validator: (value: unknown) => validationUtil.validateString(value, { required: true, pattern: /^(email|phone|twitter|facebook|intercom|custom)$/, }), }, }, }), }), }, }, }), }, }, }, }, // Add more schemas for other contact webhook events }; return WebhookValidationMiddleware.validateWebhook(eventTypeToSchemaMap); } } // Export the middleware export default WebhookValidationMiddleware;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zqushair/Frontapp-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server