Skip to content

Topics

Understanding and working with topics in RevenProx.

What are Topics?

Topics are named channels that clients subscribe to. Events published to a topic are delivered to all subscribed clients.

Topic: "user-123-notifications"
  ├── Client A (web browser)
  ├── Client B (mobile app)
  └── Client C (desktop app)

Topic Names

Format

Topic names are strings that appear in the URL path:

GET /events/{topic-name}

Examples: - /events/notifications - /events/user-123-updates - /events/room-abc-chat

Best Practices

Do Don't
Use descriptive names Use cryptic IDs alone
Include context (user, room) Use overly long names
Use URL-safe characters Include special characters
Be consistent Mix naming conventions

Good topic names:

user-{userId}-notifications
room-{roomId}-messages
system-alerts
order-{orderId}-status

Topic UUIDs

Internally, topics are identified by 128-bit UUIDs:

Topic Name: "user-123-notifications"
Topic UUID: a1b2c3d4-e5f6-7890-abcd-ef1234567890

The UUID is derived by hashing the topic name, ensuring consistent mapping.

UUID in Webhook

Your verification webhook receives the topic UUID:

{
  "jwt": "eyJhbGciOiJIUzI1NiIs...",
  "topic": "a1b2c3d4e5f67890abcdef1234567890"
}

You can use this for topic-level authorization:

// Map topic names to UUIDs for authorization
const topicUUIDs = {
  'admin-notifications': 'abc123...',
  'public-updates': 'def456...'
};

function canAccessTopic(user, topicHex) {
  const userTopics = getUserAllowedTopics(user);
  return userTopics.some(name =>
    hashToHex(name) === topicHex
  );
}

Subscribing to Topics

Single Topic

GET /events/notifications HTTP/1.1
Authorization: Bearer <token>

Multiple Topics

Connect to each topic separately:

const topics = ['alerts', 'messages', 'updates'];
const connections = topics.map(topic =>
  new EventSource(`/events/${topic}`)
);

Dynamic Topics

Generate topic names based on context:

function subscribeToUser(userId) {
  return new EventSource(`/events/user-${userId}-updates`);
}

function subscribeToRoom(roomId) {
  return new EventSource(`/events/room-${roomId}-chat`);
}

Topic Patterns

User-Specific Topics

/events/user-{userId}-notifications
/events/user-{userId}-messages

Each user subscribes to their own topic for private updates.

Resource Topics

/events/order-{orderId}-status
/events/document-{docId}-changes

Track updates to specific resources.

Broadcast Topics

/events/system-announcements
/events/global-alerts

All clients receive the same events.

Room/Channel Topics

/events/room-{roomId}-messages
/events/channel-{channelId}-activity

Group communication.

Topic Authorization

Control access to topics in your webhook:

User-Owned Topics

app.post('/verify', (req, res) => {
  const { jwt: token, topic } = req.body;
  const decoded = jwt.verify(token, JWT_SECRET);

  // User can only access their own topics
  const userTopicPrefix = hashUserId(decoded.sub);
  const isOwner = topic.startsWith(userTopicPrefix);

  res.json({
    valid: isOwner,
    user_id: decoded.sub,
    expires_at: decoded.exp
  });
});

Role-Based Access

app.post('/verify', (req, res) => {
  const { jwt: token, topic } = req.body;
  const decoded = jwt.verify(token, JWT_SECRET);

  // Check role permissions
  const topicName = uuidToTopicName(topic);
  const requiredRole = getRequiredRole(topicName);

  const hasAccess = decoded.roles.includes(requiredRole);

  res.json({
    valid: hasAccess,
    user_id: decoded.sub,
    expires_at: decoded.exp
  });
});

Permission Claims

Include topic permissions in the JWT:

{
  "sub": "user123",
  "exp": 1735689600,
  "topics": ["notifications", "room-abc-chat"]
}
app.post('/verify', (req, res) => {
  const { jwt: token, topic } = req.body;
  const decoded = jwt.verify(token, JWT_SECRET);

  // Check if topic is in allowed list
  const topicName = uuidToTopicName(topic);
  const hasAccess = decoded.topics.includes(topicName);

  res.json({
    valid: hasAccess,
    user_id: decoded.sub,
    expires_at: decoded.exp
  });
});

Topic Lifecycle

Creation

Topics are created implicitly when the first client subscribes.

Active

While at least one client is subscribed, the topic is active and receives events.

Cleanup

When the last client unsubscribes, the topic is removed from routing tables.

Distributed Topics

With multiple proxy instances, topics are synchronized:

┌─────────────┐         ┌─────────────┐
│   Proxy A   │◄───────►│   Proxy B   │
│ Topic: X    │   NNG   │ Topic: X    │
│ Clients: 2  │         │ Clients: 3  │
└─────────────┘         └─────────────┘

Events published to Topic X reach all 5 clients across both proxies.

Monitoring Topics

Active Topics Count

Track the number of active topics:

curl http://localhost:8080/metrics | grep topics_active

Subscribers Per Topic

Monitor subscriber counts for capacity planning.

Topic Churn

High topic creation/deletion rates may indicate: - Application bugs - Reconnection storms - Client misconfiguration

Best Practices

  1. Use hierarchical naming: {type}-{id}-{purpose}
  2. Keep topic names short: Reduces memory and bandwidth
  3. Avoid sensitive data in names: Topic names may be logged
  4. Implement authorization: Control who can access which topics
  5. Monitor topic metrics: Detect issues early
  6. Document topic conventions: Help developers use topics correctly

Next Steps