-
Notifications
You must be signed in to change notification settings - Fork 0
Webhook Integration
This page explains how to set up and use TMI's webhook system to receive real-time notifications when threat models, diagrams, and other resources change.
TMI's webhook system allows you to subscribe to events and receive HTTP POST notifications when those events occur. This enables real-time integrations with external systems.
Common use cases:
- Notify team channels (Slack, Teams) when threats are identified
- Create tickets in issue trackers when threats are created
- Trigger CI/CD pipelines when threat models are updated
- Send alerts to SIEM systems for critical threats
- Archive threat models to external storage
- Synchronize threat data with other security tools
The webhook system uses a worker-based architecture for reliable delivery:
Event Occurs → Redis Stream → Worker Processes → HTTP POST → Your Endpoint
↓
Status Callback (optional)
Components:
- Event Emitter: Publishes events to Redis Streams when resources change
- Event Consumer: Reads events from stream and creates delivery records
- Challenge Worker: Verifies new subscriptions using challenge-response
- Delivery Worker: Delivers webhooks with retry and exponential backoff
- Cleanup Worker: Removes old delivery records and inactive subscriptions
See the operator webhook configuration guide for deployment details.
There is an example webhook application, written in Python and designed to be hosted in AWS Lambda, available here that you can clone/fork and use as a basis to implement your own webhook applications.
Your endpoint must accept POST requests and respond with 200 OK:
Minimal example (Express.js):
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhooks/tmi', (req, res) => {
const event = req.body;
console.log('Received event:', event.event_type, event);
// Process event asynchronously
processEvent(event).catch(console.error);
// Respond immediately
res.status(200).send('OK');
});
app.listen(3000);Important: Always respond quickly (within 30 seconds) to avoid timeouts.
Create a webhook subscription via the API:
curl -X POST https://api.tmi.dev/webhook/subscriptions \
-H "Authorization: Bearer $YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "My Integration",
"url": "https://your-domain.com/webhooks/tmi",
"events": ["threat_model.created", "threat_model.updated"],
"secret": "your-shared-secret-for-hmac"
}'Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending_verification",
"name": "My Integration",
"url": "https://your-domain.com/webhooks/tmi",
"events": ["threat_model.created", "threat_model.updated"]
}TMI will send a challenge request to verify your endpoint:
Challenge request:
{
"type": "webhook.challenge",
"challenge": "abc123def456"
}Required response:
{
"challenge": "abc123def456"
}Example handler:
app.post('/webhooks/tmi', (req, res) => {
const event = req.body;
// Handle challenge
if (event.type === 'webhook.challenge') {
return res.json({ challenge: event.challenge });
}
// Handle other events
processEvent(event);
res.status(200).send('OK');
});After successful verification, your subscription becomes active and starts receiving events.
threat_model.created:
{
"event_type": "threat_model.created",
"threat_model_id": "550e8400-e29b-41d4-a716-446655440000",
"resource_id": "550e8400-e29b-41d4-a716-446655440000",
"resource_type": "threat_model",
"owner_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2025-01-15T14:30:00Z",
"data": {
"name": "Production API Threat Model",
"description": "Threat model for production API services",
"owner": "[email protected]"
}
}threat_model.updated:
{
"event_type": "threat_model.updated",
"threat_model_id": "550e8400-e29b-41d4-a716-446655440000",
"resource_id": "550e8400-e29b-41d4-a716-446655440000",
"resource_type": "threat_model",
"owner_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2025-01-15T15:45:00Z",
"data": {
"name": "Production API Threat Model - Updated",
"description": "Updated threat model",
"modified_at": "2025-01-15T15:45:00Z"
}
}threat_model.deleted:
{
"event_type": "threat_model.deleted",
"threat_model_id": "550e8400-e29b-41d4-a716-446655440000",
"resource_id": "550e8400-e29b-41d4-a716-446655440000",
"resource_type": "threat_model",
"owner_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2025-01-15T16:00:00Z",
"data": {
"name": "Production API Threat Model"
}
}diagram.created, diagram.updated, diagram.deleted:
{
"event_type": "diagram.created",
"threat_model_id": "550e8400-e29b-41d4-a716-446655440000",
"resource_id": "7d8f6e5c-4b3a-2190-8765-fedcba987654",
"resource_type": "diagram",
"owner_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2025-01-15T14:35:00Z",
"data": {
"title": "System Architecture",
"diagram_type": "data_flow"
}
}document.created, document.updated, document.deleted:
{
"event_type": "document.created",
"threat_model_id": "550e8400-e29b-41d4-a716-446655440000",
"resource_id": "abc-123-def-456",
"resource_type": "document",
"owner_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2025-01-15T14:40:00Z",
"data": {
"name": "Security Requirements.pdf",
"content_type": "application/pdf",
"size": 524288
}
}All webhook URLs must:
- Use HTTPS (required in production)
- Have valid DNS hostname
- Pass deny list checks
Blocked patterns:
- Localhost:
127.0.0.1,::1,localhost - Private IPs:
10.*,192.168.*,172.16-31.* - Link-local:
169.254.*,fe80:: - Cloud metadata endpoints (AWS, GCP, Azure, etc.)
Administrators can add custom deny list patterns via the deny list API.
Always verify signatures to ensure webhooks are from TMI:
Signature header:
X-Webhook-Signature: sha256=5d41402abc4b2a76b9719d911017c592
Verification example (Node.js):
const crypto = require('crypto');
function verifyWebhook(payloadBody, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payloadBody);
const expectedSig = `sha256=${hmac.digest('hex')}`;
// Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSig)
);
}
app.post('/webhooks/tmi', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payloadBody = JSON.stringify(req.body);
if (!verifyWebhook(payloadBody, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process webhook...
res.status(200).send('OK');
});Verification example (Python):
import hmac
import hashlib
def verify_webhook(payload_body, signature, secret):
"""Verify webhook HMAC signature"""
expected = hmac.new(
secret.encode('utf-8'),
payload_body.encode('utf-8'),
hashlib.sha256
).hexdigest()
expected_sig = f"sha256={expected}"
# Constant-time comparison
return hmac.compare_digest(signature, expected_sig)
@app.route('/webhooks/tmi', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Webhook-Signature')
payload_body = request.get_data(as_text=True)
if not verify_webhook(payload_body, signature, WEBHOOK_SECRET):
return 'Unauthorized', 401
# Process webhook...
return 'OK', 200Every webhook request includes:
POST /webhooks/tmi HTTP/1.1
Host: your-domain.com
Content-Type: application/json
X-Webhook-Event: threat_model.created
X-Webhook-Delivery-Id: 7fa85f64-5717-4562-b3fc-2c963f66afa6
X-Webhook-Subscription-Id: 7d8f6e5c-4b3a-2190-8765-fedcba987654
X-Webhook-Signature: sha256=5d41402abc4b2a76b9719d911017c592
User-Agent: TMI-Webhook/1.0Failed deliveries are retried with exponential backoff:
- Attempt 1: Immediate
- Attempt 2: After 1 minute
- Attempt 3: After 5 minutes
- Attempt 4: After 15 minutes
- Attempt 5: After 30 minutes
After 5 attempts, delivery is marked failed.
A delivery succeeds when:
- HTTP status code is 2xx (200-299)
- Response received within 30 seconds
Use X-Webhook-Delivery-Id header to detect duplicate deliveries:
const processedDeliveries = new Set();
app.post('/webhooks/tmi', (req, res) => {
const deliveryId = req.headers['x-webhook-delivery-id'];
// Check if already processed
if (processedDeliveries.has(deliveryId)) {
console.log('Duplicate delivery, skipping');
return res.status(200).send('OK');
}
// Process webhook
processEvent(req.body);
// Mark as processed
processedDeliveries.add(deliveryId);
res.status(200).send('OK');
});- Max Subscriptions: 10 active subscriptions
- Subscription Creation: 5 requests/minute, 100 requests/day
- Event Publication: 100 events/minute
Administrators can configure custom quotas:
curl -X POST https://api.tmi.dev/webhook/quotas \
-H "Authorization: Bearer $ADMIN_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"owner_id": "user-uuid",
"max_subscriptions": 50,
"max_subscription_requests_per_minute": 10,
"max_subscription_requests_per_day": 500,
"max_events_per_minute": 1000
}'Create subscription:
POST /webhook/subscriptionsList subscriptions:
GET /webhook/subscriptionsGet subscription details:
GET /webhook/subscriptions/{id}Delete subscription:
DELETE /webhook/subscriptions/{id}List deliveries:
GET /webhook/deliveriesGet delivery details:
GET /webhook/deliveries/{id}Manage deny list:
GET /webhook/deny-list
POST /webhook/deny-list
DELETE /webhook/deny-list/{id}Manage quotas:
GET /webhook/quotas/{owner_id}
POST /webhook/quotas
DELETE /webhook/quotas/{owner_id}See REST-API-Reference for complete API documentation.
-
Respond Quickly: Return 200 OK immediately, process asynchronously
app.post('/webhooks/tmi', (req, res) => { queue.add('process-webhook', req.body); res.status(200).send('OK'); });
-
Implement Idempotency: Use delivery ID to prevent duplicate processing
-
Verify Signatures: Always verify HMAC signatures
-
Handle Errors Gracefully: Return 2xx for success, 4xx/5xx for errors
-
Log Everything: Log all webhook receipts for debugging
-
HTTPS Only: Never expose HTTP endpoints (TMI will only accept https endpoints when not in development mode)
-
Strong Secrets: Use strong, random secrets (min 32 characters)
# Generate strong secret openssl rand -base64 32 -
IP Allowlisting: Consider restricting to TMI server IPs
-
Input Validation: Validate all incoming data
-
Rate Limiting: Implement rate limiting on your endpoint
-
Track Failures: Monitor subscription
publication_failures -
Check Last Success: Monitor
last_successful_usetimestamp -
Query Deliveries: Review delivery status for debugging
curl https://api.tmi.dev/webhook/deliveries \ -H "Authorization: Bearer $TOKEN" -
Set Up Alerts: Alert on high failure rates
-
Async Processing: Process webhooks asynchronously
-
Queue System: Use message queue (RabbitMQ, Redis, SQS)
-
Batch Operations: Batch database operations when possible
-
Caching: Cache frequently accessed data
-
Horizontal Scaling: Deploy multiple webhook handler instances
Send threat notifications to Slack:
const { WebClient } = require('@slack/web-api');
const slack = new WebClient(process.env.SLACK_TOKEN);
async function notifySlack(event) {
if (event.event_type === 'threat_model.created') {
await slack.chat.postMessage({
channel: '#security',
text: `New threat model created: ${event.data.name}`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*New Threat Model*\n${event.data.name}\n${event.data.description}`
}
},
{
type: 'actions',
elements: [
{
type: 'button',
text: { type: 'plain_text', text: 'View in TMI' },
url: `https://tmi.dev/threat-models/${event.threat_model_id}`
}
]
}
]
});
}
}Send email when critical threats are identified:
import smtplib
from email.mime.text import MIMEText
def send_email_notification(event):
if event['event_type'] == 'threat.created':
threat = event['data']
if threat.get('severity') == 'critical':
msg = MIMEText(f"""
Critical threat identified:
Title: {threat['title']}
Description: {threat['description']}
Threat Model: {threat['threat_model_name']}
View in TMI: https://tmi.dev/threats/{event['resource_id']}
""")
msg['Subject'] = f"[CRITICAL] {threat['title']}"
msg['From'] = '[email protected]'
msg['To'] = '[email protected]'
with smtplib.SMTP('smtp.example.com', 587) as smtp:
smtp.starttls()
smtp.login('username', 'password')
smtp.send_message(msg)Forward events to SIEM system:
import requests
def forward_to_siem(event):
"""Forward webhook event to SIEM"""
siem_endpoint = 'https://siem.example.com/api/events'
siem_event = {
'source': 'TMI',
'event_type': event['event_type'],
'timestamp': event['timestamp'],
'severity': map_severity(event),
'resource_id': event['resource_id'],
'resource_type': event['resource_type'],
'owner_id': event['owner_id'],
'data': event['data']
}
response = requests.post(
siem_endpoint,
json=siem_event,
headers={'Authorization': f'Bearer {SIEM_TOKEN}'}
)
response.raise_for_status()Checks:
- Subscription status is
active(notpending_verification) - Event types in subscription match events being triggered
- Threat model filter (
threat_model_id) is correct - Rate limit not exceeded
- Endpoint is responding with 2xx status
Debug:
# Check subscription status
curl https://api.tmi.dev/webhook/subscriptions/{id} \
-H "Authorization: Bearer $TOKEN"
# Check recent deliveries
curl https://api.tmi.dev/webhook/deliveries \
-H "Authorization: Bearer $TOKEN"Common issues:
- Endpoint not returning challenge in response body
- Response takes >30 seconds
- Non-2xx HTTP status code
- Network connectivity issues
Fix:
// Correct challenge response
app.post('/webhooks/tmi', (req, res) => {
if (req.body.type === 'webhook.challenge') {
return res.json({ challenge: req.body.challenge });
}
// ...
});Diagnose:
# Check failure reasons
curl https://api.tmi.dev/webhook/deliveries \
-H "Authorization: Bearer $TOKEN" | jq '.[] | select(.status=="failed")'Common causes:
- Endpoint unavailable or slow
- Invalid HMAC signature verification
- Endpoint returning 4xx/5xx errors
- Network issues (firewall, DNS)
Solutions:
- Improve endpoint reliability
- Fix signature verification logic
- Return 2xx for successful processing
- Check network connectivity
- Addon-System - Webhook-based add-on system
- Issue-Tracker-Integration - Integrate with issue trackers
- REST-API-Reference - Complete API documentation
- Webhook Developer Guide
- Webhook Operations Guide
- Using TMI for Threat Modeling
- Accessing TMI
- Creating Your First Threat Model
- Understanding the User Interface
- Working with Data Flow Diagrams
- Managing Threats
- Collaborative Threat Modeling
- Using Notes and Documentation
- Metadata and Extensions
- Planning Your Deployment
- Deploying TMI Server
- Deploying TMI Web Application
- Setting Up Authentication
- Database Setup
- Component Integration
- Post-Deployment
- Monitoring and Health
- Database Operations
- Security Operations
- Performance and Scaling
- Maintenance Tasks