Event System
Redis Pub/Sub event architecture, webhook:events channel, best-effort delivery, and publisher/subscriber patterns
MQUAD uses Redis Pub/Sub for event-driven communication. Kafka is NOT used.
Architecture
survey-service ──┐
├──► Redis Pub/Sub ──► webhook-service ──► External Webhooks
data-service ────┘ webhook:events │
└──► delivery records in webhook_dbChannel
All events publish to a single Redis channel: webhook:events
Event Schema
{
"event_id": "uuid-string",
"event_type": "survey.created",
"tenant_id": "tenant-uuid",
"timestamp": "2026-05-13T12:00:00.000Z",
"data": {
"survey_id": "uuid",
"survey_name": "Patient Survey"
}
}Event Producers
Survey Service
| Event | Trigger | Payload |
|---|---|---|
survey.created | Survey created | survey_id, survey_name, created_by |
survey.updated | Survey updated | survey_id, survey_name, updated_by |
survey.deleted | Survey deleted | survey_id, survey_name |
survey.published | Survey published | survey_id, survey_name, version |
survey.unpublished | Survey unpublished | survey_id, survey_name |
Data Service
| Event | Trigger | Payload |
|---|---|---|
response.submitted | Response submitted | response_id, survey_id, submitted_by |
response.deleted | Response deleted | response_id, survey_id |
response.invalidated | Response invalidated | response_id, survey_id, reason |
Best-Effort Delivery
Event publishing failures must not break the main flow:
async def publish_event(event_type, tenant_id, data):
try:
redis = get_redis_client()
event = {
"event_id": str(uuid4()),
"event_type": event_type,
"tenant_id": tenant_id,
"timestamp": datetime.utcnow().isoformat() + "Z",
"data": data
}
await redis.publish("webhook:events", json.dumps(event, default=str))
return True
except Exception as e:
logger.warning(f"Failed to publish event {event_type}: {e}")
return False # Never raise — don't break the requestEvent Consumer (Webhook Service)
The webhook-service subscribes to webhook:events and dispatches to configured URLs:
@celery_app.task
async def process_webhook_event(event_data: dict):
webhooks = await webhook_repo.find_by_event_type(
event_data["event_type"], event_data["tenant_id"]
)
for webhook in webhooks:
await deliver_webhook(webhook, event_data)