From f0440892ba1273d0e4b1e7e2d6b9fd05ab892435 Mon Sep 17 00:00:00 2001 From: Soumyadas15 Date: Fri, 20 Jun 2025 13:54:16 +0530 Subject: [PATCH] chore(dev): functional telegram webhook --- backend/webhooks/api.py | 78 +++++- backend/webhooks/models.py | 27 +- backend/webhooks/providers.py | 222 ++++++++++++++++- backend/workflows/api.py | 51 +++- backend/workflows/converter.py | 23 +- backend/workflows/models.py | 15 +- .../components/workflows/nodes/InputNode.tsx | 36 ++- .../webhooks/WebhookConfigDialog.tsx | 33 ++- .../providers/TelegramWebhookConfig.tsx | 234 ++++++++++++++++++ .../components/workflows/webhooks/types.ts | 9 +- 10 files changed, 712 insertions(+), 16 deletions(-) create mode 100644 frontend/src/components/workflows/webhooks/providers/TelegramWebhookConfig.tsx diff --git a/backend/webhooks/api.py b/backend/webhooks/api.py index 9223897b..47d50cec 100644 --- a/backend/webhooks/api.py +++ b/backend/webhooks/api.py @@ -5,8 +5,8 @@ import uuid import asyncio from datetime import datetime, timezone import json -from .models import SlackEventRequest, WebhookExecutionResult -from .providers import SlackWebhookProvider, GenericWebhookProvider +from .models import SlackEventRequest, TelegramUpdateRequest, WebhookExecutionResult +from .providers import SlackWebhookProvider, TelegramWebhookProvider, GenericWebhookProvider from workflows.models import WorkflowDefinition from services.supabase import DBConnection @@ -47,7 +47,8 @@ async def trigger_workflow_webhook( workflow_id: str, request: Request, x_slack_signature: Optional[str] = Header(None), - x_slack_request_timestamp: Optional[str] = Header(None) + x_slack_request_timestamp: Optional[str] = Header(None), + x_telegram_bot_api_secret_token: Optional[str] = Header(None) ): """Handle webhook triggers for workflows.""" try: @@ -69,11 +70,18 @@ async def trigger_workflow_webhook( logger.error(f"[Webhook] Failed to parse JSON: {e}") raise HTTPException(status_code=400, detail=f"Invalid JSON payload: {str(e)}") - provider_type = "slack" if x_slack_signature else "generic" + # Detect provider type based on headers and data structure + if x_slack_signature: + provider_type = "slack" + elif x_telegram_bot_api_secret_token or (data and "update_id" in data): + provider_type = "telegram" + else: + provider_type = "generic" logger.info(f"[Webhook] Detected provider type: {provider_type}") logger.info(f"[Webhook] Slack signature present: {bool(x_slack_signature)}") logger.info(f"[Webhook] Slack timestamp present: {bool(x_slack_request_timestamp)}") + logger.info(f"[Webhook] Telegram secret token present: {bool(x_telegram_bot_api_secret_token)}") # Handle Slack URL verification challenge first if provider_type == "slack" and data.get("type") == "url_verification": @@ -148,6 +156,8 @@ async def trigger_workflow_webhook( } else: result = await _handle_slack_webhook(workflow, data, body, x_slack_signature, x_slack_request_timestamp) + elif provider_type == "telegram": + result = await _handle_telegram_webhook(workflow, data, x_telegram_bot_api_secret_token) else: result = await _handle_generic_webhook(workflow, data) @@ -364,6 +374,66 @@ async def _handle_slack_webhook( logger.error(f"Error handling Slack webhook: {e}") raise HTTPException(status_code=400, detail=f"Error processing Slack webhook: {str(e)}") +async def _handle_telegram_webhook( + workflow: WorkflowDefinition, + data: Dict[str, Any], + secret_token: Optional[str] +) -> Dict[str, Any]: + """Handle Telegram webhook specifically.""" + try: + # Validate as TelegramUpdateRequest + telegram_update = TelegramUpdateRequest(**data) + + # Find Telegram webhook config + webhook_config = None + for trigger in workflow.triggers: + if trigger.type == 'WEBHOOK' and trigger.config.get('type') == 'telegram': + webhook_config = trigger.config + break + + if not webhook_config: + raise HTTPException(status_code=400, detail="Telegram webhook not configured for this workflow") + + # Verify secret token if configured + if webhook_config.get('telegram', {}).get('secret_token'): + expected_secret = webhook_config['telegram']['secret_token'] + if not secret_token or not TelegramWebhookProvider.verify_webhook_secret(b'', secret_token, expected_secret): + raise HTTPException(status_code=401, detail="Invalid Telegram secret token") + + payload = TelegramWebhookProvider.process_update(telegram_update) + + if payload: + execution_variables = { + "telegram_text": payload.text, + "telegram_user_id": payload.user_id, + "telegram_chat_id": payload.chat_id, + "telegram_message_id": payload.message_id, + "telegram_timestamp": payload.timestamp, + "telegram_update_type": payload.update_type, + "telegram_user_first_name": payload.user_first_name, + "telegram_user_last_name": payload.user_last_name, + "telegram_user_username": payload.user_username, + "telegram_chat_type": payload.chat_type, + "telegram_chat_title": payload.chat_title, + "trigger_type": "webhook", + "webhook_provider": "telegram" + } + + return { + "should_execute": True, + "execution_variables": execution_variables, + "trigger_data": payload.model_dump() + } + else: + return { + "should_execute": False, + "response": {"message": "Update processed but no action needed"} + } + + except Exception as e: + logger.error(f"Error handling Telegram webhook: {e}") + raise HTTPException(status_code=400, detail=f"Error processing Telegram webhook: {str(e)}") + async def _handle_generic_webhook(workflow: WorkflowDefinition, data: Dict[str, Any]) -> Dict[str, Any]: """Handle generic webhook.""" try: diff --git a/backend/webhooks/models.py b/backend/webhooks/models.py index 02407b05..8ea2af5f 100644 --- a/backend/webhooks/models.py +++ b/backend/webhooks/models.py @@ -5,7 +5,7 @@ from datetime import datetime class WebhookTriggerRequest(BaseModel): """Base webhook trigger request.""" workflow_id: str - provider: Literal['slack', 'generic'] = 'slack' + provider: Literal['slack', 'telegram', 'generic'] = 'slack' data: Dict[str, Any] headers: Optional[Dict[str, str]] = None timestamp: Optional[datetime] = None @@ -22,6 +22,17 @@ class SlackEventRequest(BaseModel): authed_users: Optional[list] = None challenge: Optional[str] = None +class TelegramUpdateRequest(BaseModel): + """Telegram update request model.""" + update_id: int + message: Optional[Dict[str, Any]] = None + edited_message: Optional[Dict[str, Any]] = None + channel_post: Optional[Dict[str, Any]] = None + edited_channel_post: Optional[Dict[str, Any]] = None + inline_query: Optional[Dict[str, Any]] = None + chosen_inline_result: Optional[Dict[str, Any]] = None + callback_query: Optional[Dict[str, Any]] = None + class SlackWebhookPayload(BaseModel): """Slack webhook payload after processing.""" text: str @@ -32,6 +43,20 @@ class SlackWebhookPayload(BaseModel): event_type: str trigger_word: Optional[str] = None +class TelegramWebhookPayload(BaseModel): + """Telegram webhook payload after processing.""" + text: str + user_id: str + chat_id: str + message_id: int + timestamp: int + update_type: str + user_first_name: Optional[str] = None + user_last_name: Optional[str] = None + user_username: Optional[str] = None + chat_type: Optional[str] = None + chat_title: Optional[str] = None + class WebhookExecutionResult(BaseModel): """Result of webhook execution.""" success: bool diff --git a/backend/webhooks/providers.py b/backend/webhooks/providers.py index fb0a0949..91fdd741 100644 --- a/backend/webhooks/providers.py +++ b/backend/webhooks/providers.py @@ -2,9 +2,10 @@ import hmac import hashlib import time import json +import httpx from typing import Dict, Any, Optional from fastapi import HTTPException -from .models import SlackEventRequest, SlackWebhookPayload +from .models import SlackEventRequest, SlackWebhookPayload, TelegramUpdateRequest, TelegramWebhookPayload from utils.logger import logger class SlackWebhookProvider: @@ -89,6 +90,225 @@ class SlackWebhookProvider: logger.error(f"Error processing Slack event: {e}") raise HTTPException(status_code=400, detail=f"Error processing Slack event: {str(e)}") +class TelegramWebhookProvider: + """Handles Telegram webhook events and verification.""" + + @staticmethod + def verify_webhook_secret(body: bytes, secret_token: str, telegram_secret_token: str) -> bool: + """Verify Telegram webhook secret token.""" + try: + return secret_token == telegram_secret_token + except Exception as e: + logger.error(f"Error verifying Telegram secret token: {e}") + return False + + @staticmethod + async def setup_webhook(bot_token: str, webhook_url: str, secret_token: Optional[str] = None) -> Dict[str, Any]: + """ + Automatically set up the Telegram webhook by calling the Telegram Bot API. + + Args: + bot_token: The Telegram bot token + webhook_url: The webhook URL to set + secret_token: Optional secret token for additional security + + Returns: + Dict containing the API response + """ + try: + telegram_api_url = f"https://api.telegram.org/bot{bot_token}/setWebhook" + + payload = { + "url": webhook_url, + "drop_pending_updates": True # Clear any pending updates + } + + if secret_token: + payload["secret_token"] = secret_token + + logger.info(f"Setting up Telegram webhook: {webhook_url}") + + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.post(telegram_api_url, json=payload) + response_data = response.json() + + if response.status_code == 200 and response_data.get("ok"): + logger.info(f"Successfully set up Telegram webhook: {response_data.get('description', 'Webhook set')}") + return { + "success": True, + "message": response_data.get("description", "Webhook set successfully"), + "response": response_data + } + else: + error_msg = response_data.get("description", f"HTTP {response.status_code}") + logger.error(f"Failed to set up Telegram webhook: {error_msg}") + return { + "success": False, + "error": error_msg, + "response": response_data + } + + except httpx.TimeoutException: + error_msg = "Timeout while connecting to Telegram API" + logger.error(f"Telegram webhook setup failed: {error_msg}") + return { + "success": False, + "error": error_msg + } + except Exception as e: + error_msg = f"Error setting up Telegram webhook: {str(e)}" + logger.error(error_msg) + return { + "success": False, + "error": error_msg + } + + @staticmethod + async def remove_webhook(bot_token: str) -> Dict[str, Any]: + """ + Remove the Telegram webhook by calling the Telegram Bot API. + + Args: + bot_token: The Telegram bot token + + Returns: + Dict containing the API response + """ + try: + telegram_api_url = f"https://api.telegram.org/bot{bot_token}/deleteWebhook" + + payload = { + "drop_pending_updates": True # Clear any pending updates + } + + logger.info("Removing Telegram webhook") + + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.post(telegram_api_url, json=payload) + response_data = response.json() + + if response.status_code == 200 and response_data.get("ok"): + logger.info(f"Successfully removed Telegram webhook: {response_data.get('description', 'Webhook removed')}") + return { + "success": True, + "message": response_data.get("description", "Webhook removed successfully"), + "response": response_data + } + else: + error_msg = response_data.get("description", f"HTTP {response.status_code}") + logger.error(f"Failed to remove Telegram webhook: {error_msg}") + return { + "success": False, + "error": error_msg, + "response": response_data + } + + except httpx.TimeoutException: + error_msg = "Timeout while connecting to Telegram API" + logger.error(f"Telegram webhook removal failed: {error_msg}") + return { + "success": False, + "error": error_msg + } + except Exception as e: + error_msg = f"Error removing Telegram webhook: {str(e)}" + logger.error(error_msg) + return { + "success": False, + "error": error_msg + } + + @staticmethod + def process_update(update_data: TelegramUpdateRequest) -> Optional[TelegramWebhookPayload]: + """Process Telegram update and extract relevant data.""" + try: + # Handle regular messages + if update_data.message: + message = update_data.message + text = message.get("text", "") + + # Skip if no text content + if not text: + logger.info("Telegram message has no text content") + return None + + user = message.get("from", {}) + chat = message.get("chat", {}) + + return TelegramWebhookPayload( + text=text, + user_id=str(user.get("id", "")), + chat_id=str(chat.get("id", "")), + message_id=message.get("message_id", 0), + timestamp=message.get("date", 0), + update_type="message", + user_first_name=user.get("first_name"), + user_last_name=user.get("last_name"), + user_username=user.get("username"), + chat_type=chat.get("type"), + chat_title=chat.get("title") + ) + + # Handle edited messages + elif update_data.edited_message: + message = update_data.edited_message + text = message.get("text", "") + + if not text: + logger.info("Telegram edited message has no text content") + return None + + user = message.get("from", {}) + chat = message.get("chat", {}) + + return TelegramWebhookPayload( + text=text, + user_id=str(user.get("id", "")), + chat_id=str(chat.get("id", "")), + message_id=message.get("message_id", 0), + timestamp=message.get("edit_date", message.get("date", 0)), + update_type="edited_message", + user_first_name=user.get("first_name"), + user_last_name=user.get("last_name"), + user_username=user.get("username"), + chat_type=chat.get("type"), + chat_title=chat.get("title") + ) + + # Handle callback queries (inline keyboard button presses) + elif update_data.callback_query: + callback = update_data.callback_query + data = callback.get("data", "") + + if not data: + logger.info("Telegram callback query has no data") + return None + + user = callback.get("from", {}) + message = callback.get("message", {}) + chat = message.get("chat", {}) if message else {} + + return TelegramWebhookPayload( + text=f"Callback: {data}", + user_id=str(user.get("id", "")), + chat_id=str(chat.get("id", "")), + message_id=message.get("message_id", 0) if message else 0, + timestamp=int(time.time()), + update_type="callback_query", + user_first_name=user.get("first_name"), + user_last_name=user.get("last_name"), + user_username=user.get("username"), + chat_type=chat.get("type"), + chat_title=chat.get("title") + ) + + logger.warning(f"Unhandled Telegram update type: {update_data.dict()}") + return None + + except Exception as e: + logger.error(f"Error processing Telegram update: {e}") + raise HTTPException(status_code=400, detail=f"Error processing Telegram update: {str(e)}") + class GenericWebhookProvider: """Handles generic webhook events.""" diff --git a/backend/workflows/api.py b/backend/workflows/api.py index c5752ddc..9de4c6e5 100644 --- a/backend/workflows/api.py +++ b/backend/workflows/api.py @@ -26,6 +26,7 @@ from scheduling.models import ( ScheduleCreateRequest, ScheduleConfig as QStashScheduleConfig, SimpleScheduleConfig, CronScheduleConfig ) +from webhooks.providers import TelegramWebhookProvider router = APIRouter() @@ -740,6 +741,19 @@ async def update_workflow_flow( # Also try to unschedule from old APScheduler as fallback await workflow_scheduler.unschedule_workflow(workflow_id) + telegram_triggers = [trigger for trigger in updated_workflow.triggers if trigger.type == 'WEBHOOK' and trigger.config.get('type') == 'telegram'] + if telegram_triggers: + try: + import os + base_url = ( + os.getenv('WEBHOOK_BASE_URL', 'http://localhost:3000') + ) + + await _setup_telegram_webhooks_for_workflow(updated_workflow, base_url) + logger.info(f"Processed Telegram webhook setup for workflow {workflow_id}") + except Exception as e: + logger.warning(f"Failed to set up Telegram webhooks for workflow {workflow_id}: {e}") + return updated_workflow except HTTPException: @@ -1106,4 +1120,39 @@ async def _remove_qstash_schedules_for_workflow(workflow_id: str): except Exception as e: logger.error(f"Failed to remove QStash schedules for workflow {workflow_id}: {e}") - raise \ No newline at end of file + raise + +async def _setup_telegram_webhooks_for_workflow(workflow: WorkflowDefinition, base_url: str): + """Set up Telegram webhooks for a workflow if configured.""" + try: + telegram_triggers = [ + trigger for trigger in workflow.triggers + if trigger.type == 'WEBHOOK' and trigger.config.get('type') == 'telegram' + ] + + for trigger in telegram_triggers: + telegram_config = trigger.config.get('telegram') + if not telegram_config: + continue + + bot_token = telegram_config.get('bot_token') + secret_token = telegram_config.get('secret_token') + + if not bot_token: + logger.warning(f"No bot token found for Telegram webhook in workflow {workflow.id}") + continue + + webhook_url = f"{base_url}/api/webhooks/trigger/{workflow.id}" + result = await TelegramWebhookProvider.setup_webhook( + bot_token=bot_token, + webhook_url=webhook_url, + secret_token=secret_token + ) + + if result.get('success'): + logger.info(f"Successfully set up Telegram webhook for workflow {workflow.id}: {result.get('message')}") + else: + logger.error(f"Failed to set up Telegram webhook for workflow {workflow.id}: {result.get('error')}") + + except Exception as e: + logger.error(f"Error setting up Telegram webhooks for workflow {workflow.id}: {e}") \ No newline at end of file diff --git a/backend/workflows/converter.py b/backend/workflows/converter.py index 0a75be86..f9b79b54 100644 --- a/backend/workflows/converter.py +++ b/backend/workflows/converter.py @@ -1,5 +1,5 @@ from typing import List, Dict, Any, Optional -from .models import WorkflowNode, WorkflowEdge, WorkflowDefinition, WorkflowStep, WorkflowTrigger, InputNodeConfig, ScheduleConfig +from .models import WorkflowNode, WorkflowEdge, WorkflowDefinition, WorkflowStep, WorkflowTrigger, InputNodeConfig, ScheduleConfig, WebhookConfig from .tool_examples import get_tools_xml_examples import uuid from utils.logger import logger @@ -143,10 +143,19 @@ class WorkflowConverter: enabled=schedule_data.get('enabled', True) ) + webhook_config = None + if data.get('trigger_type') == 'WEBHOOK' and data.get('webhook_config'): + webhook_data = data.get('webhook_config', {}) + try: + webhook_config = WebhookConfig(**webhook_data) + except Exception as e: + logger.warning(f"Failed to parse webhook config: {e}, using raw data") + webhook_config = webhook_data + return InputNodeConfig( prompt=data.get('prompt', ''), trigger_type=data.get('trigger_type', 'MANUAL'), - webhook_config=data.get('webhook_config'), + webhook_config=webhook_config, schedule_config=schedule_config, variables=data.get('variables') ) @@ -181,6 +190,14 @@ class WorkflowConverter: trigger_config['slack'] = slack_config.dict() else: trigger_config['slack'] = slack_config + elif webhook_config.type == 'telegram' and webhook_config.telegram: + telegram_config = webhook_config.telegram + if hasattr(telegram_config, 'model_dump'): + trigger_config['telegram'] = telegram_config.model_dump() + elif hasattr(telegram_config, 'dict'): + trigger_config['telegram'] = telegram_config.dict() + else: + trigger_config['telegram'] = telegram_config elif webhook_config.generic: generic_config = webhook_config.generic if hasattr(generic_config, 'model_dump'): @@ -198,6 +215,8 @@ class WorkflowConverter: if webhook_config.get('type') == 'slack' and webhook_config.get('slack'): trigger_config['slack'] = webhook_config['slack'] + elif webhook_config.get('type') == 'telegram' and webhook_config.get('telegram'): + trigger_config['telegram'] = webhook_config['telegram'] elif webhook_config.get('generic'): trigger_config['generic'] = webhook_config['generic'] diff --git a/backend/workflows/models.py b/backend/workflows/models.py index 8eb0f310..802b240b 100644 --- a/backend/workflows/models.py +++ b/backend/workflows/models.py @@ -1,5 +1,5 @@ -from pydantic import BaseModel -from typing import List, Dict, Any, Optional, Literal +from pydantic import BaseModel, field_validator +from typing import List, Dict, Any, Optional, Literal, Union from datetime import datetime class ScheduleConfig(BaseModel): @@ -19,6 +19,12 @@ class SlackWebhookConfig(BaseModel): channel: Optional[str] = None username: Optional[str] = None +class TelegramWebhookConfig(BaseModel): + """Configuration for Telegram webhook integration.""" + webhook_url: str + bot_token: str + secret_token: Optional[str] = None + class GenericWebhookConfig(BaseModel): """Configuration for generic webhook integration.""" url: str @@ -27,17 +33,18 @@ class GenericWebhookConfig(BaseModel): class WebhookConfig(BaseModel): """Configuration for webhook triggers.""" - type: Literal['slack', 'generic'] = 'slack' + type: Literal['slack', 'telegram', 'generic'] = 'slack' method: Optional[Literal['POST', 'GET', 'PUT']] = 'POST' authentication: Optional[Literal['none', 'api_key', 'bearer']] = 'none' slack: Optional[SlackWebhookConfig] = None + telegram: Optional[TelegramWebhookConfig] = None generic: Optional[GenericWebhookConfig] = None class InputNodeConfig(BaseModel): """Configuration for workflow input nodes.""" prompt: str = "" trigger_type: Literal['MANUAL', 'WEBHOOK', 'SCHEDULE'] = 'MANUAL' - webhook_config: Optional[WebhookConfig] = None + webhook_config: Optional[Union[WebhookConfig, Dict[str, Any]]] = None schedule_config: Optional[ScheduleConfig] = None variables: Optional[Dict[str, Any]] = None diff --git a/frontend/src/components/workflows/nodes/InputNode.tsx b/frontend/src/components/workflows/nodes/InputNode.tsx index 35bcea80..e5344913 100644 --- a/frontend/src/components/workflows/nodes/InputNode.tsx +++ b/frontend/src/components/workflows/nodes/InputNode.tsx @@ -96,6 +96,8 @@ const InputNode = memo(({ data, selected, id }: NodeProps) => { case 'WEBHOOK': if (nodeData.webhook_config?.type === 'slack') { return 'Slack webhook'; + } else if (nodeData.webhook_config?.type === 'telegram') { + return 'Telegram webhook'; } return `${nodeData.webhook_config?.method || 'POST'} webhook`; case 'MANUAL': @@ -200,7 +202,7 @@ const InputNode = memo(({ data, selected, id }: NodeProps) => { @@ -248,6 +251,37 @@ const InputNode = memo(({ data, selected, id }: NodeProps) => { )} )} + + {nodeData.webhook_config?.type === 'telegram' && ( +
+ + + {nodeData.webhook_config?.telegram?.webhook_url && nodeData.webhook_config?.telegram?.bot_token && ( +
+ ✓ Telegram webhook configured +
+ )} +
+ )} )} diff --git a/frontend/src/components/workflows/webhooks/WebhookConfigDialog.tsx b/frontend/src/components/workflows/webhooks/WebhookConfigDialog.tsx index 632f7076..1c6cff06 100644 --- a/frontend/src/components/workflows/webhooks/WebhookConfigDialog.tsx +++ b/frontend/src/components/workflows/webhooks/WebhookConfigDialog.tsx @@ -5,6 +5,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/u import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { SlackWebhookConfig } from "./providers/SlackWebhookConfig"; +import { TelegramWebhookConfig } from "./providers/TelegramWebhookConfig"; import { WebhookConfig } from "./types"; import { Copy, Check } from "lucide-react"; import { toast } from "sonner"; @@ -33,14 +34,28 @@ export function WebhookConfigDialog({ ); const [copied, setCopied] = useState(false); const webhookUrl = `${typeof window !== 'undefined' ? window.location.origin : process.env.NEXT_PUBLIC_FRONTEND_URL || 'http://localhost:3000'}/api/webhooks/trigger/${workflowId}`; + const handleSave = () => { if (webhookConfig.type === 'slack') { if (!webhookConfig.slack?.webhook_url || !webhookConfig.slack?.signing_secret) { toast.error("Please fill in all required Slack configuration fields"); return; } + } else if (webhookConfig.type === 'telegram') { + if (!webhookConfig.telegram?.webhook_url || !webhookConfig.telegram?.bot_token) { + toast.error("Please fill in all required Telegram configuration fields"); + return; + } } + onSave(webhookConfig); + + if (webhookConfig.type === 'telegram') { + toast.success("Telegram webhook configuration saved! The webhook will be automatically set up with Telegram."); + } else { + toast.success("Webhook configuration saved successfully!"); + } + onOpenChange(false); }; @@ -66,9 +81,14 @@ export function WebhookConfigDialog({ - setWebhookConfig(prev => ({ ...prev, type: value as 'slack' | 'generic' })) + setWebhookConfig(prev => ({ ...prev, type: value as 'slack' | 'telegram' | 'generic' })) } > + + Slack + Telegram + + + + + + setWebhookConfig(prev => ({ ...prev, telegram: telegramConfig })) + } + /> + +
+

+

+ 2 + Send the command: /newbot +

+

+ 3 + Follow the prompts to choose a name and username for your bot +

+

+ 4 + Copy the bot token from BotFather and paste it below +

+

+ 5 + Configure the bot token and secret token below +

+

+ 6 + Save the configuration - the webhook will be automatically set up! +

+

+ 7 + Start chatting with your bot to trigger workflows! +

+
+ + + +
+
+ + updateConfig('webhook_url', e.target.value)} + onBlur={(e) => validateField('webhook_url', e.target.value)} + className={getFieldStatus('webhook_url') === 'error' ? 'border-red-500' : ''} + /> + {errors.webhook_url && ( +

{errors.webhook_url}

+ )} +
+ +
+ +
+ updateConfig('bot_token', e.target.value)} + onBlur={(e) => validateField('bot_token', e.target.value)} + className={getFieldStatus('bot_token') === 'error' ? 'border-red-500 pr-10' : 'pr-10'} + /> + +
+ {errors.bot_token && ( +

{errors.bot_token}

+ )} +
+ +
+ +
+ updateConfig('secret_token', e.target.value)} + onBlur={(e) => validateField('secret_token', e.target.value)} + className={getFieldStatus('secret_token') === 'error' ? 'border-red-500 pr-10' : 'pr-10'} + /> + +
+ {errors.secret_token && ( +

{errors.secret_token}

+ )} +

+ Secret token provides additional security by verifying requests come from Telegram +

+
+
+ + ); +} \ No newline at end of file diff --git a/frontend/src/components/workflows/webhooks/types.ts b/frontend/src/components/workflows/webhooks/types.ts index ae981b32..7a9237dd 100644 --- a/frontend/src/components/workflows/webhooks/types.ts +++ b/frontend/src/components/workflows/webhooks/types.ts @@ -5,6 +5,12 @@ export interface SlackWebhookConfig { username?: string; } +export interface TelegramWebhookConfig { + webhook_url: string; + bot_token: string; + secret_token?: string; +} + export interface GenericWebhookConfig { url: string; headers?: Record; @@ -12,10 +18,11 @@ export interface GenericWebhookConfig { } export interface WebhookConfig { - type: 'slack' | 'generic'; + type: 'slack' | 'telegram' | 'generic'; method?: 'POST' | 'GET' | 'PUT'; authentication?: 'none' | 'api_key' | 'bearer'; slack?: SlackWebhookConfig; + telegram?: TelegramWebhookConfig; generic?: GenericWebhookConfig; }