from __future__ import annotations import logging logger = logging.getLogger(__name__) def parse_webhook_event_to_unified(event_type: str, payload: dict, account: dict) -> dict | None: conv = payload.get("conversation", {}) if event_type == "convo.agent.reply.created": logger.debug("Agent reply created: conv=%s — skipped", conv.get("id")) return None if event_type == "convo.ai-answers.created": logger.debug("AI Answers conversation created: conv=%s — skipped", conv.get("id")) return None if event_type == "convo.status": return _build_status_event(conv) if event_type == "convo.created": return _build_convo_created_event(conv, account) if event_type == "convo.customer.reply.created": return _build_customer_reply_event(conv, account) if event_type == "convo.note.created": return _build_note_event(conv, account) if event_type in ("beacon.chat.created", "beacon.chat.customer.replied"): return _build_beacon_chat_event(conv, account, event_type) if event_type == "satisfaction.ratings": return _build_satisfaction_event(payload, account) if event_type == "convo.assigned": return _build_assigned_event(payload) if event_type == "convo.tags": return _build_tags_event(payload) if event_type == "customer.created": return _build_customer_event(payload, action="created") if event_type == "customer.updated": return _build_customer_event(payload, action="updated") logger.debug("Unhandled webhook event type: %s", event_type) return None def _build_convo_created_event(conv: dict, account: dict) -> dict: customer = conv.get("primaryCustomer", {}) return { "channel": "helpscout", "dedup_key": f"conv:{conv['id']}:created", "event_type": "convo.created", "conversation_id": str(conv["id"]), "conversation_status": conv.get("status", "active"), "conversation_subject": conv.get("subject", ""), "sender": { "id": str(customer.get("id", "")), "type": "customer", "email": customer.get("email", ""), }, "mailbox_id": str(conv.get("mailboxId", account.get("mailbox_id"))), "timestamp": conv.get("createdAt", ""), } def _build_customer_reply_event(conv: dict, account: dict) -> dict: return { "channel": "helpscout", "dedup_key": f"conv:{conv['id']}:reply:{conv.get('threadId', '')}", "event_type": "convo.customer.reply.created", "conversation_id": str(conv["id"]), "conversation_status": conv.get("status", "active"), "conversation_subject": conv.get("subject", ""), "sender": { "id": str(conv.get("customer", {}).get("id", "")), "type": "customer", "email": conv.get("customer", {}).get("email", ""), }, "mailbox_id": str(conv.get("mailboxId", account.get("mailbox_id"))), "text": conv.get("body", "") or conv.get("preview", ""), "timestamp": conv.get("createdAt", ""), } def _build_note_event(conv: dict, account: dict) -> dict | None: return { "channel": "helpscout", "dedup_key": f"conv:{conv['id']}:note:{conv.get('threadId', '')}", "event_type": "convo.note.created", "conversation_id": str(conv["id"]), "note_text": conv.get("body", ""), "is_internal": True, "timestamp": conv.get("createdAt", ""), } def _build_status_event(conv: dict) -> dict: return { "channel": "helpscout", "dedup_key": f"conv:{conv['id']}:status:{conv.get('status', '')}", "event_type": "convo.status", "conversation_id": str(conv["id"]), "new_status": conv.get("status", ""), "timestamp": conv.get("updatedAt", ""), } def _build_beacon_chat_event(conv: dict, account: dict, event_type: str) -> dict: return { "channel": "helpscout", "dedup_key": f"beacon:{conv['id']}:{event_type}", "event_type": event_type, "conversation_id": str(conv["id"]), "conversation_type": "chat", "sender": { "id": str(conv.get("customer", {}).get("id", "")), "type": "customer", "email": conv.get("customer", {}).get("email", ""), }, "text": conv.get("body", "") or conv.get("preview", ""), "timestamp": conv.get("createdAt", ""), } def _build_satisfaction_event(payload: dict, account: dict) -> dict: rating = payload.get("rating", {}) return { "channel": "helpscout", "dedup_key": f"satisfaction:{rating.get('id', '')}", "event_type": "satisfaction.ratings", "conversation_id": str(rating.get("conversationId", "")), "rating": rating.get("rating", 0), "feedback": rating.get("feedback", ""), "timestamp": rating.get("createdAt", ""), } def _build_assigned_event(payload: dict) -> dict: conv = payload.get("conversation", {}) assignee = payload.get("assignee", {}) return { "channel": "helpscout", "dedup_key": f"conv:{conv.get('id')}:assigned:{assignee.get('id')}", "event_type": "convo.assigned", "conversation_id": str(conv.get("id", "")), "assignee_id": str(assignee.get("id", "")), "assignee_type": assignee.get("type", ""), "timestamp": conv.get("updatedAt", ""), } def _build_tags_event(payload: dict) -> dict: conv = payload.get("conversation", {}) tags = payload.get("tags", []) return { "channel": "helpscout", "dedup_key": f"conv:{conv.get('id')}:tags", "event_type": "convo.tags", "conversation_id": str(conv.get("id", "")), "tags": tags, "timestamp": conv.get("updatedAt", ""), } def _build_customer_event(payload: dict, action: str) -> dict: customer = payload.get("customer", {}) return { "channel": "helpscout", "dedup_key": f"customer:{customer.get('id')}:{action}", "event_type": f"customer.{action}", "customer_id": str(customer.get("id", "")), "email": customer.get("email", ""), "first_name": customer.get("firstName", ""), "last_name": customer.get("lastName", ""), "timestamp": customer.get(f"{'created' if action == 'created' else 'updated'}At", ""), }