The webhook system in TutorBot handles all incoming requests from Chatwoot and Stripe with sophisticated deduplication, error handling, and logging. This system ensures reliable message processing and prevents duplicate operations.
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Chatwoot β β TutorBot β β Stripe β
β β β β β β
β - Messages βββββΆβ - Webhook ββββββ - Payments β
β - Events β β - Deduplicationβ β - Webhooks β
β - Status β β - Error Handlingβ β - Events β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β Processing β
β β
β - Message β
β - Payment β
β - Calendar β
βββββββββββββββββββ
Prevents duplicate processing of the same webhook event, which can occur due to Chatwoot's retry mechanism or network issues.
# Create a unique webhook ID for idempotency
message_id = data.get("id") or data.get("message", {}).get("id")
webhook_id = f"{conversation_id}_{message_id}_{event}"
# Check if we've already processed this exact webhook
import hashlib
webhook_hash = hashlib.md5(webhook_id.encode()).hexdigest()
# Use a simple in-memory cache for webhook deduplication
if not hasattr(cw, 'processed_webhooks'):
cw.processed_webhooks = set()
if webhook_hash in cw.processed_webhooks:
print(f"π Duplicate webhook detected: {webhook_id} - skipping")
return "OK", 200
# Add to processed set (keep last 1000 webhooks)
cw.processed_webhooks.add(webhook_hash)
if len(cw.processed_webhooks) > 1000:
cw.processed_webhooks.clear() # Reset to prevent memory leaks
Unique ID Generation
Hash-Based Tracking
Memory Management
Idempotency
Problem: TypeError: object of type 'NoneType' has no len()
Root Cause: Webhook content could be None, causing length check to fail
Solution:
# Before (causing error):
message_content = data.get("content", "")[:50] + "..." if len(data.get("content", "")) > 50 else data.get("content", "")
# After (fixed):
content = data.get("content", "")
message_content = content[:50] + "..." if content and len(content) > 50 else content or ""
Defensive Programming
None valuesGraceful Degradation
Comprehensive Logging
message_createdincoming (user messages only)conversation_createdcheckout.session.completedpayment_intent.succeededdef verify_webhook(request):
"""Verify Chatwoot webhook signature"""
if not SIG:
print("β οΈ No HMAC secret configured - skipping verification")
return True
signature = request.headers.get('X-Chatwoot-Signature')
if not signature:
print("β No signature in webhook")
return False
# Verify HMAC signature
expected_signature = hmac.new(
SIG.encode(),
request.get_data(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
def verify_stripe_webhook(payload, signature):
"""Verify Stripe webhook signature"""
if not STRIPE_WEBHOOK_SECRET:
print("β οΈ No Stripe webhook secret configured")
return False
try:
event = stripe.Webhook.construct_event(
payload,
signature,
STRIPE_WEBHOOK_SECRET
)
return True
except Exception as e:
print(f"β Stripe webhook verification failed: {e}")
return False
Fast Hash Lookup
Memory Management
Early Returns
Webhook Processing Rate
Memory Usage
Error Rates
Always Handle None Values
# Good
content = data.get("content", "")
if content:
process_content(content)
# Bad
if len(data.get("content", "")) > 0:
process_content(data["content"])
Use Defensive Programming
# Good
conversation_id = data.get("conversation", {}).get("id", "unknown")
# Bad
conversation_id = data["conversation"]["id"]
Comprehensive Logging
print(f"π¨ Webhook received: {event_str} | Type: {msg_type} | Conv:{conversation_id}")
Monitor Webhook Health
Regular Maintenance
Backup and Recovery
# Chatwoot Webhook
CW_HMAC_SECRET="your_hmac_secret"
# Stripe Webhook
STRIPE_WEBHOOK_SECRET="whsec_your_stripe_secret"
# Chatwoot webhook - modules/routes/webhook.py
@app.post("/cw")
def cw():
# Main webhook handler - delegates to modules/handlers/conversation.py
from modules.handlers.conversation import handle_message_created
# ... webhook processing logic
# Stripe webhook - modules/routes/stripe.py
@app.post("/webhook/payments")
def stripe_webhook():
# Payment webhook handler - delegates to modules/handlers/payment.py
from modules.handlers.payment import verify_stripe_webhook, handle_payment_success
# ... payment processing logic
# Route registration in main.py
route_webhook.register(app)
route_stripe.register(app)
Duplicate Processing
Verification Failures
Memory Issues
# Check webhook cache size
print(f"Cache size: {len(cw.processed_webhooks)}")
# Check webhook processing
print(f"Webhook ID: {webhook_id}")
print(f"Hash: {webhook_hash}")
# Verify webhook data
print(f"Event: {event}")
print(f"Type: {msg_type}")
print(f"Content: {message_content}")
π‘ Tip: The webhook system is critical for reliable operation. Always test webhook handling thoroughly and monitor for errors in production.