Skip to main content
Glama
webhook.tsβ€’8.32 kB
import { Router, Request, Response } from 'express'; import { MemAgent } from '@core/brain/memAgent/index.js'; import { successResponse, errorResponse, ERROR_CODES } from '../utils/response.js'; import { logger } from '@core/logger/index.js'; import { randomUUID } from 'crypto'; // Simple in-memory webhook storage (in production, this should be persistent) interface WebhookData { id: string; url: string; events: string[]; active: boolean; createdAt: string; lastTriggered?: string; } const webhooks = new Map<string, WebhookData>(); export function createWebhookRoutes(_agent: MemAgent): Router { const router = Router(); /** * POST /api/webhooks * Register webhook endpoint */ router.post('/', async (req: Request, res: Response) => { try { const { url, events = ['*'] } = req.body; if (!url || typeof url !== 'string') { return errorResponse( res, ERROR_CODES.VALIDATION_ERROR, 'URL parameter is required', 400, undefined, req.requestId ); } // Validate URL format try { new URL(url); } catch { return errorResponse( res, ERROR_CODES.VALIDATION_ERROR, 'Invalid URL format', 400, undefined, req.requestId ); } // Generate unique webhook ID const webhookId = randomUUID(); const webhookData: WebhookData = { id: webhookId, url, events: Array.isArray(events) ? events : ['*'], active: true, createdAt: new Date().toISOString(), }; webhooks.set(webhookId, webhookData); logger.info('Webhook registered', { requestId: req.requestId, webhookId, url, events: webhookData.events, }); successResponse( res, { webhook: webhookData, message: 'Webhook registered successfully', }, 201, req.requestId ); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); logger.error('Webhook registration failed', { requestId: req.requestId, error: errorMsg, }); errorResponse( res, ERROR_CODES.INTERNAL_ERROR, `Webhook registration failed: ${errorMsg}`, 500, process.env.NODE_ENV === 'development' ? error : undefined, req.requestId ); } }); /** * GET /api/webhooks * List registered webhooks */ router.get('/', async (req: Request, res: Response) => { try { const webhookList = Array.from(webhooks.values()); logger.info('Listing webhooks', { requestId: req.requestId, count: webhookList.length, }); successResponse( res, { webhooks: webhookList, total: webhookList.length, timestamp: new Date().toISOString(), }, 200, req.requestId ); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); logger.error('Failed to list webhooks', { requestId: req.requestId, error: errorMsg, }); errorResponse( res, ERROR_CODES.INTERNAL_ERROR, `Failed to list webhooks: ${errorMsg}`, 500, process.env.NODE_ENV === 'development' ? error : undefined, req.requestId ); } }); /** * GET /api/webhooks/:webhookId * Get specific webhook details */ router.get('/:webhookId', async (req: Request, res: Response) => { try { const { webhookId } = req.params; if (!webhookId) { return errorResponse( res, ERROR_CODES.VALIDATION_ERROR, 'Webhook ID is required', 400, undefined, req.requestId ); } const webhook = webhooks.get(webhookId); if (!webhook) { return errorResponse( res, ERROR_CODES.NOT_FOUND, `Webhook ${webhookId} not found`, 404, undefined, req.requestId ); } logger.info('Retrieved webhook details', { requestId: req.requestId, webhookId, }); successResponse( res, { webhook, }, 200, req.requestId ); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); logger.error('Failed to get webhook details', { requestId: req.requestId, error: errorMsg, }); errorResponse( res, ERROR_CODES.INTERNAL_ERROR, `Failed to get webhook details: ${errorMsg}`, 500, process.env.NODE_ENV === 'development' ? error : undefined, req.requestId ); } }); /** * DELETE /api/webhooks/:webhookId * Remove webhook */ router.delete('/:webhookId', async (req: Request, res: Response) => { try { const { webhookId } = req.params; if (!webhookId) { return errorResponse( res, ERROR_CODES.VALIDATION_ERROR, 'Webhook ID is required', 400, undefined, req.requestId ); } const webhook = webhooks.get(webhookId); if (!webhook) { return errorResponse( res, ERROR_CODES.NOT_FOUND, `Webhook ${webhookId} not found`, 404, undefined, req.requestId ); } webhooks.delete(webhookId); logger.info('Webhook removed', { requestId: req.requestId, webhookId, }); successResponse( res, { message: `Webhook ${webhookId} removed successfully`, webhookId, }, 200, req.requestId ); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); logger.error('Failed to remove webhook', { requestId: req.requestId, error: errorMsg, }); errorResponse( res, ERROR_CODES.INTERNAL_ERROR, `Failed to remove webhook: ${errorMsg}`, 500, process.env.NODE_ENV === 'development' ? error : undefined, req.requestId ); } }); /** * POST /api/webhooks/:webhookId/test * Test webhook endpoint */ router.post('/:webhookId/test', async (req: Request, res: Response) => { try { const { webhookId } = req.params; if (!webhookId) { return errorResponse( res, ERROR_CODES.VALIDATION_ERROR, 'Webhook ID is required', 400, undefined, req.requestId ); } const webhook = webhooks.get(webhookId); if (!webhook) { return errorResponse( res, ERROR_CODES.NOT_FOUND, `Webhook ${webhookId} not found`, 404, undefined, req.requestId ); } // Perform test HTTP request to webhook URL const testPayload = { event: 'webhook_test', data: { message: 'This is a test webhook call from Cipher', timestamp: new Date().toISOString(), webhookId, }, source: 'cipher-agent', }; try { const response = await fetch(webhook.url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'User-Agent': 'Cipher-Agent-Webhook/1.0', }, body: JSON.stringify(testPayload), }); // Update last triggered time webhook.lastTriggered = new Date().toISOString(); webhooks.set(webhookId, webhook); logger.info('Webhook test completed', { requestId: req.requestId, webhookId, status: response.status, ok: response.ok, }); successResponse( res, { message: 'Webhook test completed', webhookId, testResult: { status: response.status, ok: response.ok, url: webhook.url, timestamp: new Date().toISOString(), }, }, 200, req.requestId ); } catch (fetchError) { logger.error('Webhook test failed', { requestId: req.requestId, webhookId, error: fetchError instanceof Error ? fetchError.message : String(fetchError), }); successResponse( res, { message: 'Webhook test completed with error', webhookId, testResult: { status: 0, ok: false, error: fetchError instanceof Error ? fetchError.message : String(fetchError), url: webhook.url, timestamp: new Date().toISOString(), }, }, 200, // Still return 200 as the test was executed req.requestId ); } } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); logger.error('Failed to test webhook', { requestId: req.requestId, error: errorMsg, }); errorResponse( res, ERROR_CODES.INTERNAL_ERROR, `Failed to test webhook: ${errorMsg}`, 500, process.env.NODE_ENV === 'development' ? error : undefined, req.requestId ); } }); return router; }

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/campfirein/cipher'

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