This is a sample Python application that demonstrates how to receive a webhook and convert the data into an adaptive card that is posted into a Webex space by a Webex bot. The application showcases a rocket launch notification system with rich, interactive cards that provide detailed mission information.
- π Webhook Reception - RESTful endpoint for receiving external webhook data
- π΄ Adaptive Card Generation - Dynamic creation of rich, interactive cards
- π€ Webex Bot Integration - Automated posting to Webex spaces
- π Rocket Launch Theme - Example implementation with space mission data
- β‘ Flask Web Framework - Lightweight, scalable web server
- π§ Environment Configuration - Secure credential management
- π Status Monitoring - Health check endpoint for service monitoring
- Python 3.6+
- Flask
- Requests
- Adaptive Card JSON schema
-
Clone this repository:
git clone <repository-url> cd webhook-to-card
-
Install the required packages:
pip install -r requirements.txt
-
Configure environment variables:
- Rename
.env.example
to.env
- Replace the variables with your own values:
WEBEX_ACCESS_TOKEN
: Your Webex bot access tokenWEBEX_ROOM_ID
: The ID of the Webex room where the adaptive card will be posted
- Rename
-
Start the application:
python app.py
-
Test the webhook endpoint: Send a POST request to the
/webhook
endpoint with a JSON payload that contains the data for the adaptive card.Example:
curl -X POST -H "Content-Type: application/json" -d @webhook-payload.json http://localhost:5000/webhook
-
Verify the result: The application will parse the JSON payload, generate an adaptive card, and post it into the Webex room specified by
WEBEX_ROOM_ID
.
The application expects a specific JSON structure for rocket launch data:
{
"event": "rocket_launch",
"data": {
"rocket_name": "Falcon 9 Webex",
"payload_type": "Satellite",
"payload_description": "Communications satellite for commercial use",
"launch_time": "2023-03-25T16:30:00Z",
"launch_site": "Cape Canaveral, FL",
"mission_patch": "https://example.com/mission_patch.png",
"video_stream": "https://example.com/live_stream.mp4"
}
}
Create a .env
file with the following variables:
# Webex Bot Token (from developer.webex.com)
WEBEX_BOT_TOKEN=your_bot_access_token_here
# Target Webex Room ID
WEBEX_ROOM_ID=your_room_id_here
-
Check Service Status:
curl http://localhost:5000/status
-
Send Test Webhook:
curl -X POST \ -H "Content-Type: application/json" \ -d @webhook-payload.json \ http://localhost:5000/webhook
-
Verify in Webex:
- Check your configured Webex space
- Look for the adaptive card with rocket launch details
- Interact with the "Watch the Launch" button
webhook-to-card/
βββ app.py # Main Flask application
βββ requirements.txt # Python dependencies
βββ .env.example # Environment variables template
βββ webhook-payload.json # Sample webhook data
βββ adaptive_card.json # Example adaptive card structure
βββ templates/
β βββ status.html # Status page template
βββ .gitignore # Git ignore patterns
βββ LICENSE # Cisco Sample Code License
βββ README.MD # This documentation
Component | Description | File Location |
---|---|---|
Flask App | Main web server and routing | app.py |
Webhook Handler | Processes incoming webhook data | app.py lines 20-131 |
Card Generator | Creates adaptive card from data | app.py lines 36-111 |
Webex Integration | Posts cards to Webex spaces | app.py lines 114-131 |
Status Monitor | Health check endpoint | app.py lines 134-136 |
from flask import Flask, request, jsonify, render_template
from dotenv import load_dotenv
import os, json, requests
# Load environment variables
load_dotenv()
app = Flask(__name__)
# Webex API configuration
api_url = "https://webexapis.com/v1/messages"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + os.getenv("WEBEX_BOT_TOKEN")
}
@app.route("/webhook", methods=["POST"])
def handle_webhook():
# Extract webhook data
webhook = request.get_json()
data = webhook["data"]
# Parse rocket launch details
rocket_name = data["rocket_name"]
payload_type = data["payload_type"]
payload_description = data["payload_description"]
launch_time = data["launch_time"]
launch_site = data["launch_site"]
mission_patch = data["mission_patch"]
video_stream = data["video_stream"]
# Dynamic card payload creation
card_payload = {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.3",
"body": [
{
"type": "TextBlock",
"text": "Rocket Launch Successful!",
"weight": "Bolder",
"size": "Large",
"color": "Accent",
"wrap": True
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": "auto",
"items": [
{
"type": "Image",
"url": mission_patch,
"size": "Small",
"style": "Person"
}
]
},
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "TextBlock",
"text": "Rocket Launch Details",
"weight": "Bolder",
"wrap": True
},
{
"type": "FactSet",
"facts": [
{
"title": "Rocket Name",
"value": rocket_name
},
{
"title": "Payload Type",
"value": payload_type
},
{
"title": "Launch Time",
"value": launch_time
},
{
"title": "Launch Site",
"value": launch_site
}
]
}
]
}
]
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "Watch the Launch",
"url": video_stream
}
]
}
# Message payload for Webex
message_payload = {
"roomId": os.getenv("WEBEX_ROOM_ID"),
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": card_payload
}
],
"text": "New Rocket Launch Detected"
}
# Send to Webex API
response = requests.post(api_url, headers=headers, json=message_payload)
The generated adaptive card includes:
-
Header Section:
- Bold title: "Rocket Launch Successful!"
- Accent color for visual emphasis
- Large size for prominence
-
Content Layout:
- Two-column design with mission patch and details
- Mission patch image (left column)
- Fact set with structured data (right column)
-
Interactive Elements:
- "Watch the Launch" action button
- Opens video stream in new window/tab
Field | Description | Source |
---|---|---|
Rocket Name | Launch vehicle identifier | data.rocket_name |
Payload Type | Cargo classification | data.payload_type |
Payload Description | Mission details | data.payload_description |
Launch Time | ISO 8601 timestamp | data.launch_time |
Launch Site | Geographic location | data.launch_site |
{
"type": "Image",
"url": "mission_patch_url",
"size": "Small",
"style": "Person"
}
- Store sensitive tokens in
.env
file - Never commit
.env
to version control - Use environment-specific configurations
# Secure token loading
headers = {
"Authorization": "Bearer " + os.getenv("WEBEX_BOT_TOKEN")
}
# Validate webhook structure
webhook = request.get_json()
if "data" not in webhook:
return jsonify({"error": "Invalid payload"}), 400
-
Start the Flask server:
python app.py
-
Test status endpoint:
curl http://localhost:5000/status # Expected: "Webhook Server Listening"
-
Send test webhook:
curl -X POST \ -H "Content-Type: application/json" \ -d @webhook-payload.json \ http://localhost:5000/webhook
# For production, configure proper WSGI server
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000, debug=False)
# Response validation
if response.ok:
return jsonify({"success": True}), 200
else:
return jsonify({
"success": False,
"message": response.text
}), response.status_code
# Add new fields to webhook payload
def extract_additional_data(data):
return {
"mission_status": data.get("mission_status", "Unknown"),
"weather_conditions": data.get("weather", "Clear"),
"crew_count": data.get("crew_count", 0)
}
# Template selection based on event type
def get_card_template(event_type):
templates = {
"rocket_launch": create_launch_card,
"mission_update": create_update_card,
"abort_sequence": create_abort_card
}
return templates.get(event_type, create_default_card)
# Support multiple webhook formats
@app.route("/webhook/<source>", methods=["POST"])
def handle_webhook_by_source(source):
parsers = {
"spacex": parse_spacex_webhook,
"nasa": parse_nasa_webhook,
"generic": parse_generic_webhook
}
parser = parsers.get(source, parse_generic_webhook)
return parser(request.get_json())
Flask==2.2.3 # Web framework
requests==2.28.2 # HTTP library
python-dotenv==1.0.0 # Environment variable management
Jinja2==3.1.2 # Template engine
Werkzeug==2.2.3 # WSGI utilities
certifi==2022.12.7 # SSL certificate bundle
charset-normalizer==3.0.1 # Character encoding
click==8.1.3 # Command line interface
idna==3.4 # Internationalized domain names
itsdangerous==2.1.2 # Cryptographic signing
MarkupSafe==2.1.2 # String handling
urllib3==1.26.14 # HTTP client
Issue | Solution |
---|---|
401 Unauthorized | Check WEBEX_BOT_TOKEN validity |
404 Room Not Found | Verify WEBEX_ROOM_ID exists and bot has access |
Invalid JSON | Validate webhook payload structure |
Module Not Found | Run pip install -r requirements.txt |
# Enable Flask debug mode
if __name__ == "__main__":
app.run(debug=True)
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Add to webhook handler
logger.info(f"Received webhook: {webhook}")
logger.info(f"Generated card: {card_payload}")
# .github/workflows/notify-webex.yml
name: Notify Webex on Launch
on:
push:
tags: ['v*']
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Send Launch Notification
run: |
curl -X POST \
-H "Content-Type: application/json" \
-d '{"event":"rocket_launch","data":{"rocket_name":"${{ github.ref_name }}"}}' \
${{ secrets.WEBHOOK_URL }}/webhook
# Cron job for regular status checks
*/5 * * * * curl -f http://your-domain.com/status || echo "Webhook service down"
We truly appreciate your contribution to the Webex Samples!
- Fork the repository
- Create a feature branch:
git checkout -b feature/card-enhancement
- Commit changes:
git commit -am 'Add card feature'
- Push to branch:
git push origin feature/card-enhancement
- Submit a Pull Request
- Follow PEP 8 Python style guidelines
- Add error handling for new webhook sources
- Test adaptive cards in Webex client
- Update documentation for new features
- Validate JSON schemas for new card types
This sample application was created using the following resources:
This project is licensed under the Cisco Sample Code License - see the LICENSE file for details.
For technical support and questions:
- Issues: Submit via GitHub Issues
- Adaptive Cards: Microsoft Adaptive Cards Documentation
- Webex API: Webex Developer Portal
- Community: Webex Developer Community
Made with β€οΈ by the Webex Developer Relations Team at Cisco
Note: This sample demonstrates webhook-to-card conversion for educational purposes. For production use, implement proper error handling, input validation, and security measures.