# Event System {#event-system}


MQUAD uses **Redis Pub/Sub** for event-driven communication. **Kafka is NOT used.**


## Architecture {#architecture}


```
survey-service ──┐
                 ├──► Redis Pub/Sub ──► webhook-service ──► External Webhooks
data-service ────┘     webhook:events       │
                                           └──► delivery records in webhook_db
```


## Channel {#channel}


All events publish to a single Redis channel: `webhook:events`


## Event Schema {#event-schema}


```json
{
  "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 {#event-producers}



### Survey Service {#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 {#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 {#best-effort-delivery}


Event publishing failures must not break the main flow:

```python
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 request
```


## Event Consumer (Webhook Service) {#event-consumer-webhook-service}


The webhook-service subscribes to `webhook:events` and dispatches to configured URLs:

```python
@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)
```
