Documentation Index
Fetch the complete documentation index at: https://docs.thena.ai/llms.txt
Use this file to discover all available pages before exploring further.
Comment events are published when comments are created, updated, or deleted on various entities throughout the platform. Comments can be attached to tickets, account tasks, account activities, and account notes. These events are delivered through the platform events system.
Developer quickstart
app.post("/webhook/platform-events", async (req, res) => {
const event = req.body;
if (!event?.eventId || !event?.eventType?.includes(":comment:")) {
return res.status(200).send("OK");
}
res.status(200).send("OK");
switch (event.eventType) {
case "ticket:comment:created":
await onTicketCommentCreated(event.payload);
break;
case "account-task:comment:updated":
await onAccountTaskCommentUpdated(event.payload);
break;
default:
break;
}
});
Checklist
- For public email-sourced comments, expect selective content fields (privacy).
- For large payloads, detect truncation markers and fetch full content via API.
- Handle mentions downstream; see
user:mentioned events for notifications.
Event types by entity
Comments can be associated with different entity types, and the event names reflect this:
Triggered when a comment is added to a ticket.
Triggered when a comment on a ticket is updated.
Triggered when a comment is deleted from a ticket.
Triggered when a comment is added to an account task.
Triggered when a comment on an account task is updated.
Triggered when a comment is deleted from an account task.
Triggered when a comment is added to an account activity.
Triggered when a comment on an account activity is updated.
Triggered when a comment is deleted from an account activity.
Triggered when a comment is added to an account note.
Triggered when a comment on an account note is updated.
Triggered when a comment is deleted from an account note.
interface CommentEventPayload {
comment: {
id: string;
content?: string; // May be omitted for certain visibility rules
contentHtml?: string;
contentMarkdown?: string;
contentJson?: string;
commentType: string;
parentCommentId?: string;
teamId?: string; // For ticket comments
accountId?: string; // For account-related comments
metadata: Record<string, unknown>;
customerContact?: {
id: string;
email: string;
avatarUrl: string;
};
author: {
id: string;
name: string;
email: string;
avatarUrl: string;
};
attachments: Array<{
id: string;
name: string;
url: string;
size: number;
contentType: string;
createdAt: Date;
updatedAt: Date;
deletedAt?: Date;
}>;
commentVisibility: string;
createdAt: Date;
updatedAt: Date;
deletedAt?: Date;
shouldSendEmail?: boolean; // Only for created events
createdWithTicket?: boolean; // Only for created events
};
// Entity-specific payload additions
ticket?: {
id: string;
title: string;
description?: string;
requestorEmail?: string;
submitterEmail?: string;
source: string;
isProactive?: boolean;
proactiveChannels?: string[];
};
accountTask?: {
id: string;
title: string;
};
accountActivity?: {
id: string;
};
accountNote?: {
id: string;
};
}
For update events, the payload structure includes both previous and updated states:
interface CommentUpdateEventPayload {
comment: {
previous: CommentData; // Previous state of the comment
updated: CommentData; // Updated state of the comment
};
// Same entity-specific additions as above
}
Reaction events
Comments can have reactions (emoji responses) which generate their own events:
Triggered when a reaction is added to a ticket comment.
Triggered when a reaction is removed from a ticket comment.
Reaction event payload:
interface ReactionEventPayload {
reaction: {
name: string; // Emoji name (e.g., "thumbs_up", "heart")
author: {
id: string;
name: string;
avatarUrl: string;
email: string;
};
metadata: Record<string, any>;
};
comment: {
id: string;
};
}
Content filtering and privacy
Email source content protection
For comments on tickets with email source and public visibility, certain content fields may be omitted from the platform payload for privacy reasons:
content
contentMarkdown
contentJson
Only contentHtml is included in these cases.
Large content truncation
Comments with large content (approaching the 256KB platform limit) will have their content fields truncated and replaced with “Payload too large. Use API instead”.
Event structure
All comment events follow the standard platform event structure:
interface CommentEventEnvelope {
eventId: string;
eventType: string; // e.g., "ticket:comment:created"
timestamp: string;
orgId: string;
actor: {
id: string;
type: string;
email: string;
};
payload:
| CommentEventPayload
| CommentUpdateEventPayload
| ReactionEventPayload;
}
When a comment contains user mentions, the metadata may include:
{
mentions: {
users: Array<{
id: string;
email: string;
name: string;
}>;
}
ignoreSelf: boolean; // Indicates if the author should be excluded from notifications
}
Comments created through integrations may include additional metadata:
{
source: "slack" | "email" | "api" | "web";
integrationId?: string;
externalId?: string;
syncStatus?: string;
}
Integration examples
function handleCommentCreated(payload) {
const { comment, ticket, accountTask } = payload;
// Determine notification recipients
const recipients = [];
if (ticket) {
// Notify ticket participants
recipients.push(ticket.assignedAgent);
recipients.push(ticket.customer);
} else if (accountTask) {
// Notify task assignee and watchers
recipients.push(accountTask.assignedUser);
recipients.push(...accountTask.watchers);
}
// Send notifications
recipients.forEach((recipient) => {
if (comment.shouldSendEmail) {
sendEmailNotification(recipient, comment, { ticket, accountTask });
}
sendInAppNotification(recipient, comment);
});
}
function handleCommentUpdate(payload) {
const { comment } = payload;
const { previous, updated } = comment;
// Sync to external systems
if (previous.content !== updated.content) {
// Content changed
syncContentToExternalSystems(updated);
// Log edit history
logCommentEdit({
commentId: updated.id,
previousContent: previous.content,
newContent: updated.content,
editedBy: payload.actor.id,
editedAt: updated.updatedAt,
});
}
// Update search index
updateSearchIndex(updated);
}
Reaction aggregation
function handleReactionEvent(payload) {
const { reaction, comment } = payload;
// Update reaction counts
updateReactionCounts(comment.id, reaction.name, payload.eventType);
// Notify comment author (if not self-reaction)
if (reaction.author.id !== comment.author.id) {
notifyCommentAuthor(comment.author.id, {
type: "reaction",
reaction: reaction.name,
reactorName: reaction.author.name,
commentId: comment.id,
});
}
// Track engagement metrics
trackEngagement({
type: "reaction",
reactionType: reaction.name,
commentId: comment.id,
userId: reaction.author.id,
});
}
Mention processing
function handleCommentWithMentions(payload) {
const { comment, metadata } = payload;
if (metadata?.mentions?.users) {
metadata.mentions.users.forEach((mentionedUser) => {
// Skip self-mentions if ignoreSelf is true
if (metadata.ignoreSelf && mentionedUser.id === comment.author.id) {
return;
}
// Send mention notification
sendMentionNotification(mentionedUser, {
comment,
mentionedBy: comment.author,
context:
payload.ticket || payload.accountTask || payload.accountActivity,
});
// Track mention metrics
trackMention({
mentionedUserId: mentionedUser.id,
mentionedByUserId: comment.author.id,
commentId: comment.id,
});
});
}
}
Comments support threading through the parentCommentId field:
function handleThreadedComment(payload) {
const { comment } = payload;
if (comment.parentCommentId) {
// This is a reply to another comment
const parentComment = getCommentById(comment.parentCommentId);
// Notify parent comment author
notifyCommentAuthor(parentComment.author.id, {
type: "reply",
replyId: comment.id,
replyContent: comment.content,
repliedBy: comment.author,
});
// Update thread metrics
updateThreadMetrics(comment.parentCommentId);
}
}
Attachment handling
Comments can include file attachments:
function handleCommentAttachments(payload) {
const { comment } = payload;
comment.attachments?.forEach((attachment) => {
// Process attachment
processAttachment({
id: attachment.id,
name: attachment.name,
size: attachment.size,
contentType: attachment.contentType,
url: attachment.url,
commentId: comment.id,
});
// Virus scan for new attachments
if (payload.eventType.includes("created")) {
scheduleVirusScan(attachment.id);
}
// Update storage metrics
updateStorageMetrics(attachment.size);
});
}
Best practices
- Content safety: Always sanitize comment content before displaying
- Mention handling: Respect user notification preferences for mentions
- Threading: Maintain proper parent-child relationships for threaded comments
- Privacy: Be aware of content filtering for email sources
- Performance: Consider comment volume for high-traffic tickets/entities
- Attachments: Implement proper security scanning for file attachments
Event frequency
Comment events can be high-frequency, especially for:
- Active support tickets
- Collaborative account management tasks
- Popular discussion threads
Consider implementing:
- Rate limiting for notification systems
- Batching for non-critical integrations
- Proper indexing for search systems
- Efficient storage for comment history