From 17c0bc887ceb63fe85b653e7c163b2915cdfdc1d Mon Sep 17 00:00:00 2001 From: Mayur Ughade Date: Fri, 6 Mar 2026 00:21:26 +0530 Subject: [PATCH] Add Webhook Notification Agent example demonstrating event-driven callbacks --- examples/webhook_notification_agent/README.md | 212 ++++++++++++++++++ examples/webhook_notification_agent/agent.py | 150 +++++++++++++ .../requirements.txt | 3 + .../webhook_receiver_demo.py | 158 +++++++++++++ 4 files changed, 523 insertions(+) create mode 100644 examples/webhook_notification_agent/README.md create mode 100644 examples/webhook_notification_agent/agent.py create mode 100644 examples/webhook_notification_agent/requirements.txt create mode 100644 examples/webhook_notification_agent/webhook_receiver_demo.py diff --git a/examples/webhook_notification_agent/README.md b/examples/webhook_notification_agent/README.md new file mode 100644 index 00000000..3fee4e01 --- /dev/null +++ b/examples/webhook_notification_agent/README.md @@ -0,0 +1,212 @@ +# Webhook Notification Agent + +A **Bindu AI** agent that demonstrates event-driven webhook notifications when agent tasks complete. + +## Overview + +This example shows how to integrate Bindu agents with external systems using webhooks. When the agent processes a task, it automatically sends a structured HTTP POST request to a webhook endpoint with the task result, status, and metadata. + +## How Webhook Notifications Work + +1. **Agent receives messages** via the Bindu framework +2. **Processes the task** and generates a result +3. **Sends webhook notification** with: + - Event type (`task_completed`) + - Request ID (unique identifier for tracking) + - User message content + - ISO 8601 timestamp + - Agent identifier +4. **Returns response** to the Bindu framework with webhook delivery status + +## Project Structure + +``` +webhook_notification_agent/ +├── agent.py # Main webhook notification agent +├── webhook_receiver_demo.py # Local Flask server for testing +├── requirements.txt # Python dependencies +└── README.md # This file +``` + +## Setup Instructions + +### 1. Install Dependencies + +```bash +pip install -r requirements.txt +``` + +### 2. Configure Webhook URL (Environment Variables) + +Set these environment variables before running: + +```bash +# Linux/macOS +export WEBHOOK_URL="http://localhost:5000/webhook" +export WEBHOOK_TIMEOUT="5" + +# Windows PowerShell +$env:WEBHOOK_URL = "http://localhost:5000/webhook" +$env:WEBHOOK_TIMEOUT = "5" +``` + +Default values are used if not set: +- `WEBHOOK_URL`: `http://localhost:5000/webhook` +- `WEBHOOK_TIMEOUT`: `5` seconds + +## Running the Example + +### Step 1: Start the Webhook Receiver + +In one terminal, start the demo webhook receiver server: + +```bash +python webhook_receiver_demo.py +``` + +You should see: + +``` +🚀 Starting Webhook Receiver Demo Server +====================================================================== + +Endpoints: + • POST http://localhost:5000/webhook - Receive webhooks + • GET http://localhost:5000/history - View webhook history + • POST http://localhost:5000/clear - Clear history + • GET http://localhost:5000/ - Server info + +====================================================================== +Waiting for webhooks... +``` + +### Step 2: Run the Agent + +In another terminal, test the agent locally: + +```bash +python agent.py +``` + +Or integrate it with the Bindu framework following the [official Bindu documentation](https://github.com/bindu-ai/bindu). + +### Step 3: View Webhook Notifications + +The webhook receiver will display received notifications in real-time: + +``` +====================================================================== +📬 WEBHOOK RECEIVED at 2026-03-05T10:30:45.123456Z +====================================================================== +Event: task.completed +Message: Task completed successfully: Processed: Process this test task +Timestamp: 2026-03-05T10:30:45.123456Z +Request ID: a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5g6h7 +Agent: webhook_notification_agent + +Metadata: +{ + "result": { + "status": "success", + "result": "Processed: Process this test task", + "processed_messages": 3 + }, + "message_count": 3 +} +====================================================================== +``` + +## Testing with cURL + +Send a test webhook manually: + +```bash +curl -X POST http://localhost:5000/webhook \ + -H "Content-Type: application/json" \ + -d '{ + "event": "task_completed", + "request_id": "test-123", + "message": "Manual test webhook", + "timestamp": "2026-03-05T10:00:00Z", + "agent": "webhook_notification_agent" + }' +``` + +## Example Webhook Payload + +The agent sends webhooks with this structure: + +```json +{ + "event": "task_completed", + "request_id": "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5g6h7", + "message": "Process this webhook test task", + "timestamp": "2026-03-05T10:30:45.123456Z", + "agent": "webhook_notification_agent" +} +``` + +**Response from agent.py:** + +```json +{ + "status": "success", + "response": { + "request_id": "a1b2c3d4-e5f6-4789-a0b1-c2d3e4f5g6h7", + "webhook": { + "status": "sent", + "status_code": 200 + } + } +} +``` + +## Features Demonstrated + +✅ **Event-driven architecture** - Webhooks trigger on task completion +✅ **Structured payloads** - Consistent JSON format with metadata +✅ **Error handling** - Graceful handling of network and timeout errors +✅ **Timeout protection** - Configurable timeout for webhook calls +✅ **Request tracking** - Unique request IDs for correlation +✅ **Bindu integration** - Official `bindufy` pattern +✅ **Local testing** - Flask demo server for quick testing + +## Integration with External Services + +Replace the `WEBHOOK_URL` with any webhook service: + +- **Slack**: `https://hooks.slack.com/services/YOUR/WEBHOOK/URL` +- **Discord**: `https://discord.com/api/webhooks/YOUR/WEBHOOK/ID/TOKEN` +- **Teams**: `https://outlook.office.com/webhook/YOUR/WEBHOOK/URL` +- **Zapier**: `https://hooks.zapier.com/hooks/catch/YOUR/WEBHOOK/ID` +- **Custom API**: Any endpoint accepting POST requests + +## Production Considerations + +When deploying to production: + +1. **Use environment variables** for webhook URLs +2. **Enable HTTPS** for secure webhook delivery +3. **Implement retry logic** for failed webhook deliveries +4. **Add authentication** (e.g., HMAC signatures, API keys) +5. **Log webhook events** for audit trails +6. **Monitor webhook performance** and failure rates +7. **Consider async webhooks** for high-volume scenarios + +## Dependencies + +- `bindu` - Bindu AI agent framework +- `requests` - HTTP library for webhook delivery +- `flask` - Web framework for demo receiver (dev only) + +## License + +This example is part of the Bindu AI framework and follows the same license terms. + +## Contributing + +Contributions are welcome! Please open an issue or pull request on the [Bindu GitHub repository](https://github.com/bindu-ai/bindu). + +--- + +**Built with ❤️ for the Bindu AI community** diff --git a/examples/webhook_notification_agent/agent.py b/examples/webhook_notification_agent/agent.py new file mode 100644 index 00000000..50313090 --- /dev/null +++ b/examples/webhook_notification_agent/agent.py @@ -0,0 +1,150 @@ +""" +Push Notification Webhook Agent for Bindu AI Framework + +Example agent that sends webhook notifications when tasks complete. +Demonstrates event-driven integration with external systems. +""" + +from typing import Any, Dict, List +import os +import json +import uuid +import requests +from datetime import datetime + +from bindu.penguin.bindufy import bindufy + + +CONFIG = { + "name": "webhook_notification_agent", + "description": "Example Bindu agent that sends a webhook notification when a task completes.", + "author": "Mayur Ughade", + "deployment": { + "url": "http://localhost:3773", + "expose": False + }, + "skills": [] +} + + +# Webhook Configuration (environment variables with defaults) +WEBHOOK_URL = os.getenv("WEBHOOK_URL", "http://localhost:5000/webhook") +WEBHOOK_TIMEOUT = int(os.getenv("WEBHOOK_TIMEOUT", "5")) + + +def send_webhook(payload: Dict[str, Any]) -> Dict[str, Any]: + """ + Send webhook notification to external system. + + Args: + payload: Dictionary containing event data + + Returns: + Dictionary with status and result information + """ + try: + response = requests.post( + WEBHOOK_URL, + json=payload, + timeout=WEBHOOK_TIMEOUT, + headers={"Content-Type": "application/json"} + ) + response.raise_for_status() + + return { + "status": "sent", + "status_code": response.status_code + } + + except requests.exceptions.Timeout: + return { + "status": "failed", + "error": "webhook_timeout" + } + + except requests.exceptions.RequestException as e: + return { + "status": "failed", + "error": f"http_error: {str(e)}" + } + + except Exception as e: + return { + "status": "failed", + "error": str(e) + } + + +def handler(messages: List[Dict[str, str]]) -> Dict[str, Any]: + """ + Official Bindu handler contract. + + Receives messages, processes them, and sends a webhook notification. + + Args: + messages: List of message dictionaries with 'role' and 'content' + + Returns: + Dictionary with status and response data + """ + try: + # Input validation + if not messages: + return { + "status": "error", + "error": "no_messages_provided" + } + + user_message = messages[-1].get("content") + + if not user_message: + return { + "status": "error", + "error": "no_message_content" + } + + request_id = str(uuid.uuid4()) + + # Build event payload + event_payload = { + "event": "task_completed", + "request_id": request_id, + "message": user_message, + "timestamp": datetime.utcnow().isoformat() + "Z", + "agent": CONFIG["name"] + } + + # Send webhook + webhook_result = send_webhook(event_payload) + + return { + "status": "success", + "response": { + "request_id": request_id, + "webhook": webhook_result + } + } + + except Exception as e: + return { + "status": "error", + "error": str(e) + } + + +# Register the agent with Bindu +bindufy(CONFIG, handler) + + +if __name__ == "__main__": + # Test the agent locally + print(f"Testing {CONFIG['name']}...") + print("-" * 50) + + test_messages = [ + {"role": "user", "content": "Process this webhook test task"} + ] + + result = handler(test_messages) + print("\nAgent Response:") + print(json.dumps(result, indent=2)) diff --git a/examples/webhook_notification_agent/requirements.txt b/examples/webhook_notification_agent/requirements.txt new file mode 100644 index 00000000..0514afa8 --- /dev/null +++ b/examples/webhook_notification_agent/requirements.txt @@ -0,0 +1,3 @@ +bindu +requests +flask diff --git a/examples/webhook_notification_agent/webhook_receiver_demo.py b/examples/webhook_notification_agent/webhook_receiver_demo.py new file mode 100644 index 00000000..1209916a --- /dev/null +++ b/examples/webhook_notification_agent/webhook_receiver_demo.py @@ -0,0 +1,158 @@ +""" +Webhook Receiver Demo Server + +A simple Flask server to receive and display webhook notifications +from the Bindu webhook notification agent. + +This demo server helps you test the webhook agent locally without +needing external webhook services. +""" + +import json +from datetime import datetime + +from flask import Flask, request, jsonify + + +app = Flask(__name__) + + +# Store received webhooks in memory for demo purposes +webhook_history = [] + + +@app.route("/webhook", methods=["POST"]) +def webhook_endpoint(): + """ + Webhook endpoint that receives POST requests from the agent. + + Returns: + JSON response with acknowledgment + """ + try: + # Get the webhook payload + payload = request.get_json() + + if not payload: + return jsonify({ + "status": "error", + "message": "No JSON payload received" + }), 400 + + # Log the received webhook + received_at = datetime.utcnow().isoformat() + "Z" + webhook_entry = { + "received_at": received_at, + "payload": payload + } + webhook_history.append(webhook_entry) + + # Print webhook details to console + print("\n" + "=" * 70) + print(f"📬 WEBHOOK RECEIVED at {received_at}") + print("=" * 70) + print(f"Event: {payload.get('event', 'N/A')}") + print(f"Message: {payload.get('message', 'N/A')}") + print(f"Timestamp: {payload.get('timestamp', 'N/A')}") + print(f"Request ID: {payload.get('request_id', 'N/A')}") + print(f"Agent: {payload.get('agent', 'N/A')}") + + if payload.get('metadata'): + print("\nMetadata:") + print(json.dumps(payload['metadata'], indent=2)) + + print("=" * 70 + "\n") + + # Return acknowledgment + return jsonify({ + "status": "success", + "message": "Webhook received successfully", + "received_at": received_at + }), 200 + + except Exception as e: + print(f"\n✗ Error processing webhook: {type(e).__name__} - {str(e)}\n") + return jsonify({ + "status": "error", + "message": str(e) + }), 500 + + +@app.route("/history", methods=["GET"]) +def get_history(): + """ + Get the history of received webhooks. + + Returns: + JSON array of all received webhooks + """ + return jsonify({ + "total": len(webhook_history), + "webhooks": webhook_history + }) + + +@app.route("/clear", methods=["POST"]) +def clear_history(): + """ + Clear the webhook history. + + Returns: + JSON response with confirmation + """ + global webhook_history + count = len(webhook_history) + webhook_history = [] + + print(f"\n🗑️ Cleared {count} webhook(s) from history\n") + + return jsonify({ + "status": "success", + "message": f"Cleared {count} webhook(s)" + }) + + +@app.route("/", methods=["GET"]) +def index(): + """ + Simple index page with server info. + + Returns: + JSON with server status + """ + return jsonify({ + "service": "Webhook Receiver Demo", + "status": "running", + "endpoints": { + "webhook": "/webhook (POST)", + "history": "/history (GET)", + "clear": "/clear (POST)" + }, + "webhooks_received": len(webhook_history) + }) + + +def main(): + """ + Start the webhook receiver demo server. + """ + print("\n" + "=" * 70) + print("🚀 Starting Webhook Receiver Demo Server") + print("=" * 70) + print("\nEndpoints:") + print(" • POST http://localhost:5000/webhook - Receive webhooks") + print(" • GET http://localhost:5000/history - View webhook history") + print(" • POST http://localhost:5000/clear - Clear history") + print(" • GET http://localhost:5000/ - Server info") + print("\n" + "=" * 70) + print("Waiting for webhooks...\n") + + app.run( + host="0.0.0.0", + port=5000, + debug=False + ) + + +if __name__ == "__main__": + main()