Raven Docs

Reactions

Idempotent, pre-grouped, and deliberately unvalidated.

await chat.messages.addReaction('msg_3xRโ€ฆ', '๐Ÿ‘');
await chat.messages.removeReaction('msg_3xRโ€ฆ', '๐Ÿ‘');
 
chat.on('reactionAdded', (event) => {
  console.log(`${event.userId} reacted ${event.emoji}`);
});

Idempotent in both directions

Adding the same reaction twice is a no-op, not a duplicate. Removing one that isn't there succeeds. Both are guaranteed by a unique constraint on (messageId, userId, emoji), so a double-tap on a flaky connection is harmless โ€” and reactions are the most double-tapped control in any chat UI, often clicked on exactly the connection quality where a request might get retried.

Grouped, not raw

Messages carry reactions already grouped per emoji:

"reactions": [
  { "emoji": "๐Ÿ‘", "count": 3, "userIds": ["alice", "bob", "carol"] },
  { "emoji": "๐ŸŽ‰", "count": 1, "userIds": ["dave"] }
]

Clients want "๐Ÿ‘ ร—3 (alice, bob, carol)", not three rows to group themselves โ€” doing it server-side means every transport agrees on the shape. userIds lets you render "you reacted" state and a hover tooltip without a second request.

Limits

Up to 200 reactions per message, and 32 characters per emoji โ€” enough for any real emoji, including multi-codepoint sequences like ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ, and short enough that the field can't become a second message body.

Raven doesn't validate that a reaction is an emoji. Products use custom reactions, :shipit:-style shortcodes, and image keys โ€” an allow-list would break all of them to prevent nothing.

Common errors

ErrorWhyFix
Reaction silently doesn't appear twiceIdempotent by design โ€” the second addReaction for the same (message, user, emoji) is a no-op, not an error.Expected โ€” read it as success, not a bug.
INVALID_MESSAGE/validation error on an over-length emoji fieldOver the 32-character limit.Cap custom shortcode length client-side before sending.

Production notes

  • Render from the message's own reactions array, not a locally accumulated count โ€” a reconnect replaying reactionAdded events you already counted would double them.
  • Disable a reaction button while its own call is pending (React: useReactions()'s pending) rather than relying on idempotency alone to absorb a rapid double-tap.